c语言学习第7天

变量的属性:
作用域:变量可以使用的范围。
生命周期:变量从定义到释放的时间段。
存储位置:变量使用的是哪个内存段。

程序的内存分段:
text datd bss:代码编译完成后就确定下来了,在程序运行期间不会发生变化,使用size命令可以查看可执行文件的这三个内存段大小。
heap stack:它们会随机程序的运行而动态变化,在程序没有运行前是无法查看的,在程序运行期间,查看/proc/进程号/maps文件,里面记录着程序的内存使用情况,getpid()函数可以查看当前程序的进程号。
text 代码段:分为两部分,由于它们都是只读的,所以把它们俩当作一个内存段。
r-x: 编译器会把C代码编译成二进制指令,当程序执行时,二进制指令会被加载到该内存段,它决定的程序如何执行。
r–:常量数据会存储在该内存段。
这两块内存如果被强制修改,会产生段错误。
data 全局数据段:该内存段存储的是初始化过的全局变量、静态变量。
bss 静态数据段:该内存存储的是未初始化过的全局变量、静态变量,在程序运行前,该内存段的所有字节会被初始化为0,所以全局变量、静态变量的默认值是0。
heap 堆:由程序员手动操作的内存段,特点是足够大,但使用麻烦,当数据量比较大时使用该内存段。
stack 栈:该内存段存储的是局部变量和块变量,由系统自动管理使用方便(自动分配、释放),但容量有限,在Linux系统下可以通过 ulimit -s 查看栈内存的容量上限,我们使用的虚拟机是8192KB,超出这个范围就会出现段错误或栈崩溃。

变量的分类:
全局变量:定义在函数外的变量。
作用域:整个程序中都可以使用。
生命周期:从程序运行开始 到 程序运行结束释放。
存储位置:初始化过的存储在data段,未初始化的存储在bss段。
局部变量:定义在函数内的变量。
作用域:只能在它所在的函数内使用。
生命周期:从函数被调用且执行变量的定义语句开始 到 函数执行结束后释放。
存储位置:栈
块变量:定义在 if、for、while、do while 语句块内的变量。
作用域:只能在它所在的语句块的大括号内使用。
生命周期:从函数被调用且执行变量的定义语句开始 到 语句块的大括号释放。
存储位置:栈
注意:全局变量、局部变量、块变量是可以重名的,局部变量会屏蔽同名的全局变量,块变量会屏蔽同名的全局变量和局部变量。

全局变量的优缺点:
优点:
1、使用范围广,在程序内的任何位置都可以使用,方便。
2、函数之间可以共享全局变量,避免了函数之间传参,可以提高程序的运行效率。
缺点:
1、可能会有命名冲突,全局变量会与其它文件中的全局变量、结构、联合、枚举、宏、函数名、类等命名冲突。
2、全局变量在整个程序的运行期间不会被释放,会造成一定的内存浪费。
结论:尽量少用,最好不用。

修饰变量的关键字:
auto:早期用来定义自己分配、释放内存的变量(局部变量、块变量),但随着实际编程中局部变量、块变量使用的越来越多,C标准委员会决定,该关键字不加就代表加,就像signed关键字一样,因此它现在在C语言中没有什么用。
auto int num; // NO 它不能修饰全局变量,因为在整个程序运行期间全局变量不会被释放。
int main()
{
auto int num; // OK
return 0;
}
扩展:在C++11语法标准中auto老树开花,它可以根据初始化数据,自动设置变量的类型,所以在面试、笔试时,要搞清楚面试的岗位是C开发还是C++开发。
typedef:变量一旦被typedef修饰,就会变成一种类型,如果类型名字比较长,可以使用typedef重新取一个简短的名字,比如:typedef unsigned long size_t,像这样的例子在stdint.h头文件中还有很多。
const:它给变量提供一种保护机制,被它修饰过的变量不能显式修改,但可以通过迂回方式修改,所以被const修饰过的变量不是常量,因为真正的常量被修改时会现段错误。
注意:如果面试时问到const关键字,它的主要功能是保护变量,而不是把变量变成常量。
特例:存储在data段的变量如果被const修饰,存储位置就会被改为text,就变成了真正的常量,一旦被强行修饰就会产生段错误。
int num; // bss
int num = 1234; // data
const int num = 1234; // text
int main()
{
int num; // stack
const int num; // stack
}
多个.c文件如何编译:
1、把每个.c文件编译成以.o结尾的目标文件
gcc -c xxx.c
2、把若干个.o文件合并成一个可执行文件
gcc a.o b.o … 生成a.out文件
3、./a.out 执行程序

static:该关键字有三大主要功能:限制作用域,改变存储位置,延长生命周期。
限制作用域:把全局变量、函数的作用域限制为只能在它所在的.c文件内使用。
例如:在b.c中定义的函数、全局变量默认情况下可以在a.c中使用(使用前需要先声明),如果b.c中的函数、全局变量被static修饰,a.c中就不能再使用了。
改变存储位置:static如何修饰了局部变量、块变量,会把它们的存储位置由stack改为data或bss。
延长生命周期:static如何修饰了局部变量、块变量后,由于它们的存储位置变成了data或bss,所以它们的生命周期也将会与全局变量一样,并且默认值也一样是0。
注意:const、static 在面试时问的很多,要重点掌握。
计算机的存储介质:
机械硬盘、固态硬盘、内存条、CPU中的高级缓存、CPU中的寄存器,一般普通变量都存储在内存条中。

register:被它修饰的变量,会申请把存储介质由内存条改为寄存器,如果申请成功该变量的使用速度会有质的提升,这种变量也被称为寄存器变量,为了不暴露寄存器的位置,这种变量不能取地址。
由于寄存器的数量有限,申请不一定百分百成功。
变量的使用过程:
int num;
printf(“num = %d\n”,num); // 当在代码中第一次使用变量的值时,系统会从内存中读取变量的值。
… // 在这个时段,num的值没有变化
printf(“num = %d\n”,num); // 再次使用变量时,系统不会再次从内存中读取变量的值,而是直接使用上次读取到的,这叫作变量的读值优化。
问题:如果num是被多"人"共享的,读值优化就会造成num的值与内存中的不符。

volatile:该单词的意思是不稳定的、易变的,当变量被它修饰后,相当于告诉系统,这个变量被多人共享,它的值随机可能发生变化,不要优化变量的读值过程,每次用到这个变量时都从内存中读取变量的值。
int num = 1234;
if(num == num)
{
// 该条件永远为真,因为第二次使用num时,系统不会从内存读取它的值,而是使用第一次读取到的,即使num的内存被其它"人"修改。
}
volatile int num = 1234;
if(num == num)
{
// 该条件有可能为假,num被volatile修饰后,第次用到num系统都会从内存中读取它的值,如果第一次和第二次读取的间隔中间,num被其它"人"修改,那么两次读取到的值会不一样。
}
什么情况下需要使用 volatile 关键字:
1、多个线程共享一个变量时,这个变量就应该被volatile修饰。
2、驱动编程、裸机编程时(硬件与软件之间共享一个数据区域)。
注意:volatile 关键字用的不多,但笔试和面试时经常考,因为对于 volatile 关键字的理解能体现出你对C语言的掌握程序。
extern:用于声明全局变量,如果a.c使用了b.c中的全局变量,就需要在a.c中声明一下b.c中的全局变量。

你可能感兴趣的:(c语言,学习)