C语言-内存、类型限定符

一、程序的内存分段(进程映像):

​ 当执行程序的运行命令后,操作系统会给程序分配它所需要的内存,并划分成以下内存段供程序使用:

text 代码段:

​ C代码被翻译成二进制指令后存储在可执行文件中,当可执行文件被操作系统执行时,它会把里面的二进制指令(编译后的代码)加载到这个内存段,它里面的内容决定了程序如何执行,为了避免程序被破坏、修改,所以它的权限是只读。
​ 该内存段分为两个部分:
r-x:二进制指令
r--:常量数据
注意:该内存段的内容如果被强制修改会产生段错误(非法使用内存)。

data 数据段:

​ 存储的是初始化过(初始化的值非零)的全局变量
​ 存储在该内存段的变量,被const修饰后,就会改存储到text内存段,变成真正的常量。

bss 静态数据段:

​ 存储的是未初始化的全局变量
​ 操作系统把程序被加载到内存后,会把该内存段进行初始化,也就是所有字节赋值为零,所以全局变量的默认值不是随机,而是零。

heap 堆:

​ 该内存段由程序员手动调用内存管理函数(malloc/free),进行分配、释放,它的分配释放受程序员的控制,适合存储一些需要长期使用的数据。
​ 它的大小不受限制,理论上能达到物理的上限,所以适合存储大量的数据。
​ 该内存段无法取名字,也就是无法与标识符建立联系,必须与指针配合使用。

stack 栈:

​ 存储的是局部变量、块变量
​ 该内存段会随着程序的执行自动的分配(定义局部变量、块变量)、释放(函数执行完毕自动释放局部变量、块变量),虽然使用比较方便,但它的释放不受程序员控制,长期使用的数据不能存储在栈内存中。

​ 该内存的大小有限,在终端执行: ulimit -s 可以查看当前系统栈内存的使用上限,使用虚拟机ubuntu的栈内存使用上限是8192kb,一旦超过这个限制就会产生段错误。可以使用ulimit -s <size> 命令设置栈内存的使用上限。

静态内存:

​ 当程序完成编译 textdatabss 三个内存段的大小就确定,在程序运行期间大小不会有任何变化,可以使用size命令查看程序的这三个内存段的大小。

size ./a.out
   text	   data	    bss	    dec	    hex	filename
   3884	    312	     96	   4292	   10c4	./a.out

动态内存:

heapstack两个内存段,会随着程序的执行,而动态变化。
​ 当程序运行时,/proc/程序编号/maps文件里记录程序执行过程中内存的使用情况,程序运行结束这个文件就消失了。
​ 使用ps aux命令查看所有进程的编号,getpid()函数可以获取当前进程的编号。

二、变量属性和分类

变量的属性

  • 作用域:变量的使用范围。
  • 存储位置:变量使用那个内存段存储数据,决定了变量在运行期间能否被释放(销毁),能否被修改。
  • 生命周期:变量从定义、分配内存到内存销毁的时间段。

全局变量:

​ 定义在函数外的变量叫全局变量。

  • 作用域:本程序内任何位置都可以使用。
  • 存储位置:初始化的全局变量使用的是data内存段,未初始化的全局变量使用的是bss内存段。
  • 生命周期:从程序开始执行,到程序执行结束。

局部变量:

​ 定义在函数内的变量叫局部变量。

  • 作用域:只能在它所在的函数内使用(从定义的位置开始,到函数结束)。
  • 存储位置:使用的是stack内存段。
  • 生命周期:当它所在的函数被调用后,执行到局部变量的定义语句时局部变量就会被创建(操作系统会给局部变量的变量名分配一块stack内存),当函数执行结束后,局部变量就被销毁了。

块变量:

​ 定义在ifforwhiledo while语句块内的变量叫局部变量,就是特殊的局部变量。

  • 作用域:只能在它所在的语句块内使用。
  • 存储位置:使用的是stack内存段。
  • 生命周期:当它所在的函数被调用后,执行到块变量的定义语句时块变量就会被创建(操作系统会给块变量的变量名分配一块stack内存),当出了它所在的大括号,块变量就被销毁了。

注意:全局变量、局部变量、块变量可以同名,不会造成命名冲突,局部变量会屏蔽同名的全局变量,块变量会屏蔽同名的全局变量、局部变量。
解决: 一般为了解决全局变量与局部变量命名冲突问题,全局变量一般首字母大写,局部变量一般全部小写

全局变量的优点和缺点:

优点:使用方便,避免了函数之间传参产生的消耗,提高程序的运行速度。

缺点

  1. 程序运行期间全局变量所占用的内存不会被销毁,可能会产生内存浪费。
  2. 命名冲突的可能性比较大,可能会与其它文件的全局变量、函数、结构、联合、枚举、宏命名冲突。
#include <stdio.h>

int scanf;	//	全局变量,很容易起命名冲突
int main() {
    int scanf;	//	局部变量 不容易起冲突
}

总结:全局变量尽量少用,或者不用。

三、修饰变量的关键字——类型限定符

<类型限定符> 数据类型 变量名;

typedef

typedef int num;
num n1;	//n1 就是int类型

​ 变量名被typedef修饰后,就会变成定义它的数据类型,此时该名字不是变量名而是类型名,之后就可以使用这种新的数据类型定义变量、数组了,该功能是为了给复杂的数据类型重新定义一个简短的类型名。
​ 由于无符号整型使用比较麻烦,所以标准库中为我们定义一些简短的无符号整型的类型名,就使用typedef定义的,实现在stdint.h头文件里。

typedef signed char     int8_t;
typedef short int       int16_t;
typedef int         	int32_t;
typedef long long int   int64_t;

typedef unsigned char       uint8_t;
typedef unsigned short int  uint16_t;
typedef unsigned int        uint32_t;
typedef unsigned long long int  uint64_t;

auto

auto int num;

​ 早期的C语言用它来修饰自动分配、释放内存的变量,也就是局部变量和块变量,但由于代码使用的变量绝大多数都是局部变量和块变量,所以就约定,该关键字不加就代码加,所以该关键字已经没有实用价值了。

​ 在C++11的语法标准中,auto有了新的功能,就是定义自动类型的变量,编译器会根据变量的初始值,自动设置变量的数据类型。
​ auto num = 1234; // num int类型
​ auto f = 3.14; // f double类型
编译指令:g++ xxx.c -std=c++11
注意:虽然auto关键字,已经不再使用,但基本功能还保留着,所以它不能修饰全局变量。

const

const int num;

const的意思是常量,但实际它只是为变量提供一层保护,被它修饰的变量不能显式修改,但可以隐式修改,也就被它修饰后并不能变成真正的常量。

#include <stdio.h>
int main() {
    const int num = 10; 
    int *p = (int *)&num;                                               
    *p = 88; 
    //num = 88;
    printf("%d\n",num);
}

注意:存储在data内存段的变量,被const修饰后就会变成真正的常量,存储位置被修改为text,其实是修改了data段和text段的分界线。如果就算隐式修改也会段错误

static

static既可以修饰变量,也可以修饰函数,主要有三大功能:

限制作用域:

​ 默认情况下全局变量、函数的作用域是整个程序都可以使用,被static修饰后,就只能在它所在的.c文件内使用。
​ 该功能可以避免全局变量、函数的命令冲突,也能防止全局变量、函数被外部修改、调用,提高代码的安全性。
​ 普通全局变量、函数也叫外部变量、外部函数,被static修饰后就叫做内部变量、内部函数、静态全局变量。

改存储位置:

​ 局部变量、块变量被static修饰后,存储位置就由stackdata、bss,称呼为静态局部变量、静态块变量。
​ 静态局部变量、静态块变量的默认值不再是随机的,而是零。

延长生命周期:

​ 由于静态局部变量、静态块变量的存储位置由stack(动态分配、释放)改为data、bss,所以静态局部变量、静态块变量不会随着函数的执行结束而销毁,而是和全局变量的生成周期一样。

注意static修饰局部变量、块变量,会改变它们的存储、延长生命周期,但并不会改变它们的作用域。

volatile

​ 在程序中使用到变量时,系统会从内存中读取该变量的值交给CPU运算,如果之后该变量的值没有发生明显变化,再次使用变量时系统会直接使用上次读取的旧值,而不会再从内存中读取。这编译器对变量读值过程的优化。
volatile关键字就告诉编译器不要优化变量的读值过程,每使用该变量时,都重新从内存中读取它的值。

int num = 10;
if (num == num) {} // 一定成立

volatile int num = 20;
if (num == num) {} // 有可能不成立

​ 什么情况下需要使用volatile关键字:
​ 变量被共享访问,且有多个执行者可以修改它的值,这种情况下变量就应该被volatile修饰。
情况1:多线程编程处理复杂问题时。
情况2:裸机编程、驱动编程时,软硬件共用的寄存器。

register

​ 计算机的存储介质读写速度排序:机械硬盘->固态硬盘->内存条->高级缓存->CPU寄存器
register关键字的作用是申请把变量的存储介质由内存条改为CPU寄存器,一旦申请成功,变量的读写速度、运算速度会大大提高。

注意:CPU中的寄存器数量有限,申请不一定成功,只有需要长期大量运算的变量才适合用register关键字修饰。
注意:register修饰过的变量,不能获取变量的地址。

extern

​ 当使用其它.c文件中的全局变量时,需要像声明函数一样,对其它.c文件全局变量进行声明。

extern 类型 变量名; 

注意:声明变量只能解决编译时的问题,如果目标文件最终链接时,变量没有定义,依然会报错。

a.c:(.text+0x12):对‘num’未定义的引用,这种是链接时的错误。

上一篇:数组、函数及位运算

下一篇:指针

posted @   sleeeeeping  阅读(27)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
  1. 1 吹梦到西洲 恋恋故人难,黄诗扶,妖扬
  2. 2 敢归云间宿 三无Marblue
吹梦到西洲 - 恋恋故人难,黄诗扶,妖扬
00:00 / 00:00
An audio error has occurred, player will skip forward in 2 seconds.

作词 : 颀鞍

作曲 : 铃木航海

编曲 : 远藤直弥/冯帆

制作人 : 冯帆/铃木航海

(妖扬)

(妖扬)

无何化有 感物知春秋

秋毫濡沫欲绸缪 搦管相留

(黄诗扶)

留骨攒峰 留容映水秀

留观四时曾邂逅 佳人西洲

(妖扬)

(妖扬)

西洲何有 远树平高丘

云闲方外雨不收 稚子牵牛

(黄诗扶)

闹市无声 百态阴晴栩栩侔

藤衣半卷苔衣皱 岁月自无忧

(妖扬)

(妖扬)

驾马驱车 尚几程扶摇入画中 咫尺

(黄诗扶)

径曲桥横 精诚难通

(黄诗扶、妖扬)

(黄诗扶、妖扬)

盼你渡口 待你桥头

松香接地走

挥癯龙绣虎出怀袖

起微石落海连波动

描数曲箜篌线同轴

勒笔烟直大漠 沧浪盘虬

一纸淋漓漫点方圆透

记我 长风万里绕指未相勾

形生意成 此意 逍遥不游

(妖扬)

(妖扬)

日月何寿 江海滴更漏

爱向人间借朝暮 悲喜为酬

(黄诗扶)

种柳春莺 知它风尘不可救

绵绵更在三生后 谁隔世读关鸠

(妖扬)

(妖扬)

诗说红豆 遍南国未见人长久 见多少

(黄诗扶)

来时芳华 去时白头

(黄诗扶、妖扬)

(黄诗扶、妖扬)

忘你不舍 寻你不休

画外人易朽

似浓淡相间色相构

染冰雪先披琉璃胄

蘸朱紫将登金银楼

天命碧城灰土 刀弓褐锈

举手夜古泼断青蓝右

照我 萤灯嫁昼只影归洪流

身魂如寄 此世 逍遥不游

(黄诗扶)

(黄诗扶)

情一物 无木成林无水行舟

情一事 未算藏谋真还谬

情一人 积深不厚积年不旧

情一念 墨尽非空 百代飞白骤 划地为囚

(妖扬)

(妖扬)

蓝田需汲酒 惟琼浆能浇美玉瘦

至高者清难垢 至贵者润因愁

痴竭火 知她不能求

醉逢歌 知他不必候

只约灵犀过隙灵光暗相投

(黄诗扶、妖扬)

(黄诗扶、妖扬)

万籁停吹奏

支颐听秋水问蜉蝣

既玄冥不可量北斗

却何信相思最温柔

顾盼花发鸿蒙 怦然而梦

你与二十八宿皆回眸

系我 彩翼鲸尾红丝天地周

情之所至 此心 逍遥不游

吉他 : ShadOw

钢琴 : ShadOw

和声编写 : 冯帆

和声 : 黄诗扶

人声混音 : 徐志明

混音 : 冯帆

母带 : 冯帆

企划 : 三糙文化

出品公司 : Negia Entertainment Inc.

点击右上角即可分享
微信分享提示