从内核角度理解C的声明、定义与初始化
链接:https://zhuanlan.zhihu.com/p/377901599
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
C语言的声明
/* 变量声明 */
int value;
/* 结构体声明 */
struct student {
    int number;
    float score;
    char *name;
};声明在英文中是declaration,即宣告的意思,意即告诉编译器,这个东西已经存在,以后再遇到不要不认识它。声明变量的本质是给一段内存命名,"int value"即表示将4个字节的内存空间命名为value,在做出这样的声明以后,编译器在以后识别到value后,就知道value代表4个字节的内存空间。结构体的声明同理,"struct student"可以理解为我们自定义了一种新的数据模子,它的作用和int类似,int表示4个字节,而struct student则表示16个字节,即告诉编译器struct student是一个16字节的数据类型。所以声明的作用旨在向编译器传递一些编译信息。
但是,声明不会分配内存。这里分配内存的具体含义是虚存映射(Linux内存管理术语)。虚存映射的作用在于明确进程可以合法访问的地址空间范围,如果访问了没有映射的虚存空间,运行时就会收到段错误(Segment Fault)。这是因为Linux的物理内存分配机制是请页机制,它是一种惰性分配机制。在C程序里面申请的内存,无论是在堆区还是栈区,申请的内存都是假象,其只是虚拟内存区(VMA),内核中用vm_area_struct结构体表示,并没有对应到实际的物理内存。当处理器首次访问这个虚拟地址时,通过硬件标志位发现该虚拟地址没有对应的物理内存,这会触发处理器的缺页异常,然后由内核进行处理。内核在缺页异常的处理过程中,会首先检查虚拟地址是否是一个合法地址,即判断虚拟地址是否在映射的VMA范围内,如果不在,就结束处理,并返回段错误;如果在,则通过伙伴系统分配物理页,此时才获得真正的物理内存。所以,这里内存分配的实际含义即是指虚存映射,接下来就不再强调。
对于局部变量(自动变量),编译器为其分配内存的方式是在栈上开辟空间。接下来,通过一个简单的实验来观察变量声明和变量定义的区别。
int main(void)
{
        /* 变量定义 */
        int tmp = 99;
        /* 变量声明 */
	int value;
	/* 结构体声明 */
	struct student {
            int number;
            float score;
            char *name;
        };
	
	return 0;
}对应的汇编代码如下:
 
可以看到,在程序中声明的变量value和结构体student都没有分配内存,只有定义的变量tmp编译器为其在栈上开辟了4字节的空间。
再思考一个问题,如果说变量只声明后没有分配内存,也即没有进行虚存映射,那在程序中访问这个变量的内存会不会引发段错误呢?接下来改动一下代码,在程序中访问一个只声明的变量的内存:
#include <stdio.h>
int main(void)
{
        /* 变量定义 */
        int tmp = 99;
        /* 变量声明 */
	int value;
        printf("%d\n", value); /* 访问只声明的变量内存 */
	
	return 0;
}运行结果:
hds@ubuntu:~/github/c-code/code/stack$ ./stackoverflow 
0运行结果显示并没有收到段错误,说明变量所含内存空间是合法的,已经进行了虚存映射。再对这段程序汇编,查看堆栈的变化:
 
可以看到,编译器在栈上开辟了8个字节的空间,这其中高4个字节是定义tmp变量分配的,低4个字节则是分配给了value变量。虽然我们在初始时,只声明了value,但是在printf函数中对其进行了访问,这个操作会让编译器为value变量分配内存。
但是,如果一个自动变量在声明后没有任何赋值或者访问操作,那么编译器不会为其分配内存。
C语言的定义
/* 变量定义 */
value = 99;
/* 结构体定义 */
struct student stu;与声明不同,编译器会在变量定义时为其分配内存。这里我们主要观察结构体的定义,继续通过一个小实验来观察:
int main(void)
{
        /* 变量定义 */
        int tmp = 99;
        /* 变量声明 */
	int value;
	/* 结构体声明 */
	struct student {
            int number;
            float score;
            char *name;
        };
        /* 结构体定义 */
        struct student stu;
	
	return 0;
}在程序里定义了一个结构体变量stu,接下来汇编这段代码查看堆栈的变化:
 
从对应的汇编代码中,可以看到编译器在栈上开辟了20个字节的空间,这20个字节包含了tmp变量的4个字节,剩余16个字节即是为结构体变量stu所分配的内存空间。这里为了方便讨论问题,已经对结构体成员进行了字节对齐。
所以,对于结构体类型,其声明时不会分配内存。在定义结构体变量时,编译器才会为其分配内存。
C语言的初始化
/* 变量初始化 */
value = 99;
/* 结构体初始化 */
stu.number = 88;初始化就是对变量进行赋值。对于自动变量,定义即是初始化。但对于结构体类型,定义和初始化并不等同。结构体定义后编译器会为结构体变量在栈上分配内存,结构体初始化则进一步向某个成员所对应的内存空间写入值。
继续通过一个小实验来观察:
int main(void)
{
        /* 变量定义 */
        int tmp = 99;
        /* 变量声明 */
	int value;
	/* 结构体声明 */
	struct student {
            int number;
            float score;
            char *name;
        };
        /* 结构体定义 */
        struct student stu;
        /* 结构体初始化 */
        stu.number = 88;
	
	return 0;
}对应的汇编代码:
 
总结
- 自动变量和结构体的声明不分配内存,在定义后才会分配内存
- 自动变量定义即是初始化
- 结构体的定义和初始化含义不同
 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号