首先讲解一下c语言的编译阶段和运行阶段的含义。

(1) c语言的编译阶段(静态一次性过程)

主要涉及到点击交叉编译工具链构建代码以及现在比较友好IDE实时语法错误提醒,这些都属于编译阶段。交叉编译工具链主要的工作分为预处理、编译器编译成汇编目标文件、汇编器编译成机器目标文件、链接器根据链接脚本将前面编译生成的每一个文件中的各个段:.BSS(.ZI)、.data、.text根据链接脚本将各个段进进行组合生成一个可执行文件。

预处理器的预处理操作只是进行文本展开、文本替换、删除注释等操作,不涉及语法检查。

编译器和汇编器主要是涉及到语法检测和类型检查。语法检查主要是检查代码段(各个函数)是否存在语法错误,例如代码结束缺少分号,不规范操作等。

变量类型检查,变量类型检查要求各个源文件中的.BSS(.ZI)、.data变量占用内存大小是确定的,不能是未知的或者变动的,或者将来被改动的,必须要一次编译确定下来。最后将源代码翻译成汇编代码,以及将汇编代码翻译成机器代码。以前文章中提到的VLA(variable length array)可变长度数组定义成全局变量后,之所以在编译阶段(keil MDK直接提示错误)报错就是因此代码段占用内存在编译阶段必须要定下来,而且无法修改,但是VLA的数组长度在代码运行阶段是有可能导致变动,就算是最初定义了一个初始值,也无法保证在代码运行是不会修改该变量的值,该变量的值如果发生变动,全局数组长度也应该发生变动,这也就影响了这个可执行文件的内存布局分布,这与编译阶段数据段各个变量占用内存多少是一次性确定下来的,不会修改的相矛盾。因此,编译器在编译阶段对VLA全局变量报编译错误。

链接器:主要是将编译器和链接器生成的各个目标文件中的各个不同的段,根据自定义链接脚本或者默认链接脚本将各个段进行统一摆放,代码段放在一起,数据段放在一起,BSS段放在一起,最后形成一个可执行文件。

(2)c语言代码的运行阶段(动态过程)

将可执行文件下载flash中,CPU调用flash中的机器码到内存中,开始执行代码。C语言代码的运行阶段可以看做CPU跳转到函数内部执行函数内部代码的过程。执行函数内部代码,就涉及到函数内部临时局部栈变量的申请,申请多大内存是有栈变量大小确定的,栈空间是共用的一段内存,只要不超限就没问题,申请的栈内存大小是有函数临时变量的类型确定的,栈变量和堆变量占用栈内存和占用堆内存大小是可以变动的,只要保证不超限即可。我们分析一下函数内部定义VLA(可变长度数组),为什么在编译阶段和运行阶段不报错。

void write_flash(uint32_t flash_addr,uint16_t flash_dat)
{
    uint8_t dd_len = 10;
    uint8_t dd[dd_len];
    
	if(flash_addr > 0x803E000)
	{
		HAL_FLASH_Unlock();
		HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, flash_addr, flash_dat);
		HAL_FLASH_Lock();
	}
}

编译阶段:首先语法检查是没有问题的,其次是类型检查当编译器读到可变长度数组时,dd_len变量已经申请栈变量,并且里面写入有效数值10,当编译检查VLA时,实际变成了uint8_t dd[10],因此申请多大栈变量也是确定的。

运行阶段:算函数write_flash()函数后面修改了dd_len的值,也无法再影响栈内存的分配,如果后面的代买将这两行代码写在一个例如for循环中,每次都还能运行这2行代码,修改dd_len的值后会根据dd_len的值动态分配栈变量,也是允许的。

总之:c语言的编译阶段是一个静态过程(涉及到数据段,强制要求确定下来,不能再发生改变),处理过一次内存分布定型后不运行修改,这也是VLA如果定义成全局变量编译报错原因(因为可变长度数组企图修改内存布局排布)

c语言的运行阶段是一个动态分配内存的过程(涉及到堆内存和栈内存),允许多次修改堆内存或者栈内存空间大小,只要不超限就不会有问题

c语言的编译阶段对内存的检查和申请是一次性的涉及到数据段;c语言的运行阶段对内存的申请可以是多次,涉及到堆内存和栈内存。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐