如何将指定变量或函数编译至固定的内存区域?
1. 内存类型
在我们讨论这个问题之前,需要先了解一下C语言中内存中有哪些类型:
1.1 bss段(Block Started by Symbol)
bss段属于静态内存分配,通常是指用来存放程序中未初始化的(或初始化为0的)全局变量和静态变量的一块内存区域。在程序执行之前bss段会自动清0,所以,未初始的全局变量在程序执行之前已经成0了。
bss段在执行文件中时候不占磁盘空间,要运行的时候才分配空间并清0。
特点是:可读写的,
1.2 data段(data segment)
数据段通常是指用来存放程序中已初始化的全局或静态变量的一块内存区域。
数据段属于静态内存分配。
1.3 text段(code segment/text segment)
text段通常是指用来存放程序执行代码的一块内存区域。
这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读(某些架构也允许代码段为可写,即允许修改程序)。
在代码段中,也有可能=包含一些只读的常数变量,例如字符串常量等。
1.4 dec
dec 是text,data和bss的算术和。
1.5 堆(heap):
堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。
当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);
堆是先进先出(FIFO)数据结构,当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。
注意:堆内存需要程序员手动管理内存,通常适用于较大的内存分配,如频繁的分配较小的内存,容易导致内存碎片化。
1.6 栈(stack):
栈又称堆栈,是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。
除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出(FIFO)特点,所以栈特别方便用来保存/恢复调用现场。
从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。
注意:由于栈的空间通常比较小,一般 linux 程序只有几 M,故局部变量,函数入参应该避免出现超大栈内存使用,比如超大结构体,数组等,避免出现 stack overflow。
2 链接脚本
什么是链接脚本?链接脚本有什么作用?
链接脚本:用来描述程序是如何在内存空间中分布的(指定.text .data .bss在内存中的地址)。编译器会根据链接脚本输出可执行文件。
ARM编译器用的scat格式的链接脚本,gcc编译器用的是ld格式的脚本,Tricore使用的是lsl格式的链接脚本,不同脚本语法是不一样的。
链接脚本的作用:将多个目标文件(.o)和库文件(.a)链接成一个可执行文件(输出文件),并控制输出文件的内存布局(地址分配)。
注: GCC在编译C语言文件的时候,会分别生成RO、RW、ZI部分。RO是只读段,也就是程序代码段(.text),就是具体函数代码;RW是读写数据段(.data),也就是初始化的全局变量;ZI为未初始化数据段(.bss),也就是那些未赋初值的变量,这个段不占用ROM空间,只有在程序运行的时候在RAM初始化为0。
链接脚本的作用也就是将这些编译出来的段整合到一起。
2.1 ld链接脚本
ld链接脚本由MEMORY和SECTIONS,还有一些链接配置组成:
- 链接配置(有的配置项目是可选的);