【C语言之高级编程】如何将指定变量或函数编译至固定的内存区域中?

如何将指定变量或函数编译至固定的内存区域?

  • 1. 内存类型
    • 1.1 bss段(Block Started by Symbol)
    • 1.2 data段(data segment)
    • 1.3 text段(code segment/text segment)
    • 1.4 dec
    • 1.5 堆(heap):
    • 1.6 栈(stack):
  • 2 链接脚本
    • 2.1 ld链接脚本
      • 2.1.1 链接配置
      • 2.1.2 MEMORY
      • 2.1.3 定位符'.'
      • 2.1.4 SECTION
      • 2.1.5 其他常用命令
      • 2.1.6 链接脚本完整示例
    • 2.2 ARM核内如何把指定函数编译到固定内存(使用ld链接脚本)
    • 2.3 lsl链接脚本

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链接脚本由MEMORYSECTIONS,还有一些链接配置组成:

  • 链接配置(有的配置项目是可选的);
  • MEMORY可选的,如果没有,链接器则认为所有输入文件都位于同一个内存区域,并且从0x0开始;
  • SECTIONS是必须的;

2.1.1 链接配置

如一些符号变量的定义,入口地址,输出格式等;

STACK_SIZE  = 0x200
ENTRY(_START)
OUTPUT_ARCH(arm)
OUTPUT_FORMAT(elf32_littlearm)

ENTRY命令
ENTRY(begincode) 代表我们使用 begincode 作为程序入口地址,链接器会默认使用第一个可执行section作为程序入口点,即start。

ENTRY(Reset_Handler)将Reset_Handler函数设为程序的入口点,链接器需要知道程序的入口点,才能正确地组织可执行文件的时序,才能正确地执行编译、链接、优化代码。

ld有多种指定程序入口方式(优先级逐渐降低):
a、ld命令 -e选项;
b、连接脚本中ENTRY(symbol)的命令;
c、如果定义了_start符号,使用_start符号的值;
d、如果存在.text段,则使用.text段的第一字节的地址;
e、使用地址0x00000000;

2.1.2 MEMORY

MEMORY为链接器提供系统内存的布局信息,并确定内存区域的访问权限。链接器根据MEMORY的信息,将编译生成的.o目标文件中的代码、数据、符号等分配到不同的内存区域。

脚本格式如下:

MEMORY
{/*标准格式如下*/mem_name [(attr)] : ORIGIN = origin, LENGTH = lenRAM0 (xrw)  : ORIGION = (0x00000000), LENGTH = 2MRAM1 (xrw)  : ORIGION = (0x30000000), LENGTH = 128M
}

mem_name 是一块内存的名称,自定义,但不得重复,仅在ld文件中生效。

attr字符串是可选的属性列表:

RRead-only section只读段
WRead/write section读写段
XExecutable section可执行段
AAllocatable section可分配段
IInitialized section初始化段
LInitialized section同上
!Invert the sense of any of the preceding attributes反转任何前述属性的含义

关键字ORIGION : 区域的开始地址
关键字LENGTH : 区域的大小

而后,链接脚本可声明将指定的代码放到对应的memory区域——以下链接脚本代码将.text段存放到标号为RAM的内存区域,将.data段存放到标号为FLASH的内存区域:

SECTIONS
{.text >RAM   /*.text也可以后面再细分*/.data >FLASH
}

2.1.3 定位符’.’

‘.’ 表示当前地址,它可以被赋值,也可以赋值给某个变量。

如下就是将当前地址赋值给某个变量(链接器是按照SECTIONS里段顺序排列的,前面排完之后才能计算出当前地址)

RAM_START  =  . ;

如下就是将段放在特定的地址中:

SECTIONS
{. = 0x10000; /*将所有目标文件的text段从0x10000地址开始存放 */.text :{*(.text)}. = 0x80000000;.data : {*(.data)}
}

2.1.4 SECTION

SECTION的基本命令语法如下:

SECTIONS
{	/*标准格式如下*/section-name [address] [(type)] :[AT(lma)][ALIGN(section_align)][SUBALIGN(subsection_align)][constraint]{contentscontents...} [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp]
}

这么多参数中,只有section-name和contents是必须的,链接脚本的本质是描述输入和输出的关系。secname表示输出文件的段,即输出文件中有那些段,而contents就是描述输出文件的这个段从那些文件里抽取出来的,即输入文件。

region指的是mem_name即内存名(VMA地址)。

输出段名
section-name输出段名必须符合输出格式的约束,例如a.out 输出格式包含’.text’ ‘.data’ ‘.bss’ 三个输出段名。

输出段属性(节点属性)
NOLOAD 该部分应标记为不可加载,以便在程序运行时不会将其加载到内存中。
DSECT
COPY
INFO
OVERLAY 支持这些类型名称以实现向后兼容,并且很少使用。 它们都具有相同的效果:该段应标记为不可分配,以便在程序运行时不为该段分配内存。

输出段地址
每个段都有LMA(加载内存地址)和VMA(虚拟内存地址)
LMA = load memory address
VMA = vitual memory address

LMA就是程序放置的地址,VMA就是运行时的地址。
如果程序是在ram里运行,但程序是存储在flash里,则运行地址指向ram,而加载地址指向flash。

下述例子中VMA为RAM,LMA为FLASH;
[>region] 指定输出段分布在内存上的地址。
[AT(lma)] 指定该段的加载地址。另外,您也可以使用[AT>lma_region]表达式指定
在这里插入图片描述
如果未为输出段指定AT或AT>,则链接器将设置当前段的VMA和LMA与同一区域中的先前输出段的设置相同。 如果没有前面的输段或该段不可分配,则链接器会将LMA与VMA设置为相同的值。

输入段 描述
输入段描述由一个文件名(可选)和一个圆括号内的段名称列表组成。

例如:
*(EXCLUDE_FILE (*crtend.o) .ctors) EXCLUDE_FILE(文件列表)表示剔除指定的输入文件,即不包含这些文件的指定段。
*(.text .rdata) 这种方法两个段顺序是不定的。
*(.text) *(.rdata) 这种方法两个段顺序是固定的。
data.o(.data)指定某个文件的某个段。

注: " * "符号在前一般是取址符,在后则是指通配符。

2.1.5 其他常用命令

强制对齐 ALIGN
强制输出对齐ALIGN(n),n一般是2的幂次方;
强制输入对齐SUBALIGN, 指定的值会覆盖输入段给出的任何对齐方式。

PROVIDE
PROVIDE关键字可以被用来定义一个符号(相当于定义了一个全局变量),比如’etext’, 这个定义只在它被引用到的时候有效,而在它被定义的时候无效。PROVIDE定义的符号,允许C语言中重定义(但是不能带前导下划线),重定义后优先使用C中的定义。

SECTIONS
{.text :{*(.text)PROVIDE(etext = .);}
}

KEEP
KEEP(*(.Vectors))
作用是防止垃圾收集机制把重要的节排除在外(防止被优化),也保证了KEEP对象在段中的位置处于最顶端。
ABSOLUTE
PROVIDE(_bss_over = ABSOLUTE(.));
将当前地址的绝对值赋值给_bss_over

2.1.6 链接脚本完整示例

ENTRY(Reset_Handler)_Min_Heap_Size = 0x200;      /* required amount of heap  */
_Min_Stack_Size = 0x1500; /* required amount of stack */MEMORY
{FLASH (rx)      : ORIGIN = 0x8000000, LENGTH = 128KRAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 32K
}SECTIONS
{.isr_vector :{. = ALIGN(4);KEEP(*(.isr_vector)) /* Startup code */. = ALIGN(4);} >FLASH.text :{. = ALIGN(4);*(.text)           /* .text sections (code) */*(.text*)          /* .text* sections (code) */*(.glue_7)         /* glue arm to thumb code */*(.glue_7t)        /* glue thumb to arm code */*(.eh_frame)KEEP (*(.init))KEEP (*(.fini)). = ALIGN(4);__fsymtab_start = .;KEEP(*(FSymTab))__fsymtab_end = .;. = ALIGN(4);__vsymtab_start = .;KEEP(*(VSymTab))__vsymtab_end = .;. = ALIGN(4);__rt_init_start = .;KEEP(*(SORT(.rti_fn*)))__rt_init_end = .;. = ALIGN(4);_etext = .;        /* define a global symbols at end of code */_exit = .;} >FLASH.rodata :{. = ALIGN(4);*(.rodata)         /* .rodata sections (constants, strings, etc.) */*(.rodata*)        /* .rodata* sections (constants, strings, etc.) */. = ALIGN(4);_shell_command_start = .;KEEP (*(shellCommand))_shell_command_end = .;} >FLASH.ARM.extab   : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH.ARM : {__exidx_start = .;*(.ARM.exidx*)__exidx_end = .;} >FLASH.preinit_array     :{PROVIDE_HIDDEN (__preinit_array_start = .);KEEP (*(.preinit_array*))PROVIDE_HIDDEN (__preinit_array_end = .);} >FLASH.init_array :{PROVIDE_HIDDEN (__init_array_start = .);KEEP (*(SORT(.init_array.*)))KEEP (*(.init_array*))PROVIDE_HIDDEN (__init_array_end = .);} >FLASH.fini_array :{PROVIDE_HIDDEN (__fini_array_start = .);KEEP (*(SORT(.fini_array.*)))KEEP (*(.fini_array*))PROVIDE_HIDDEN (__fini_array_end = .);} >FLASH_sidata = LOADADDR(.data);.data : {. = ALIGN(4);_sdata = .;        /* create a global symbol at data start */*(.data)           /* .data sections */*(.data*)          /* .data* sections */. = ALIGN(4);_edata = .;        /* define a global symbol at data end */} >RAM AT> FLASH. = ALIGN(4);.bss :{/* This is used by the startup in order to initialize the .bss secion */_sbss = .;         /* define a global symbol at bss start */__bss_start__ = _sbss;*(.bss)*(.bss*)*(COMMON). = ALIGN(4);_ebss = .;         /* define a global symbol at bss end */__bss_end__ = _ebss;} >RAM/* User_heap_stack section, used to check that there is enough RAM left */._user_heap_stack :{. = ALIGN(4);PROVIDE ( end = . );PROVIDE ( _end = . );. = . + _Min_Heap_Size;. = . + _Min_Stack_Size;_estack = .;. = ALIGN(4);} >RAM/* Remove information from the standard libraries *//DISCARD/ :{libc.a ( * )libm.a ( * )libgcc.a ( * )}.ARM.attributes 0 : { *(.ARM.attributes) }
}

2.2 ARM核内如何把指定函数编译到固定内存(使用ld链接脚本)

问: 基于ARM核 ,把指定函数 MyFunction 编译到固定的内存区域中(起始地址为0x7D000000,大小为0x2000)?
我的答案如下:

/* ld file start */ 
MEMORY 
{EXT_RAM   : ORIGIN = 0xa0000000,       LENGTH = 0x8000EXT_ROM   : ORIGIN = 0x7E680000,       LENGTH = 0x16180EXT_BSS   : ORIGIN = 0x7FC98000,       LENGTH = 0x3C00MY_SECTION : ORIGIN = 0x7D000000,      LENGTH = 0x2000;
}SECTIONS 
{. = 0x7D000000
.text : 
{*(.text.*)} > MY_SECTION
}/* ld file end */ /* C file start */ 
Void MyFunction(void) __attribute__((section(".text.myfunci")));
Void MyFunction(void){//implement
};
/* C file end */ 

2.3 lsl链接脚本

参见TC3xx完整工程搭建之链接文件还有基于Tricore的Tasking链接文件解读, 和上述的ld链接脚本有异曲同工之处,但有的地方也更加复杂一点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/373845.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【人工智能】Transformers之Pipeline(概述):30w+大模型极简应用

​​​​​​​ 目录 一、引言 二、pipeline库 2.1 概述 2.2 使用task实例化pipeline对象 2.2.1 基于task实例化“自动语音识别” 2.2.2 task列表 2.2.3 task默认模型 2.3 使用model实例化pipeline对象 2.3.1 基于model实例化“自动语音识别” 2.3.2 查看model与task…

谷粒商城学习笔记-22-分布式组件-SpringCloud-OpenFeign测试远程调用

文章目录 一,OpenFeign的简介二,OpenFeign的使用步骤1,场景说明2,引入依赖2,开启OpenFeign3,编写Feign接口4,使用feign调用远程接口5,验证 错误记录 上一节学习了注册中心&#xff0…

区块链论文速读A会-ISSTA 2023(2/2)如何检测DeFi协议中的价格操纵漏洞

Conference:ACM SIGSOFT International Symposium on Software Testing and Analysis (ISSTA) CCF level:CCF A Categories:Software Engineering/System Software/Programming Languages Year:2023 第1~5篇区块链文章 请点击此…

讲讲 JVM 的内存结构(附上Demo讲解)

讲讲 JVM 的内存结构 什么是 JVM 内存结构?线程私有程序计数器​虚拟机栈本地方法栈 线程共享堆​方法区​注意永久代​元空间​运行时常量池​直接内存​ 代码详解 什么是 JVM 内存结构? JVM内存结构分为5大区域,程序计数器、虚拟机栈、本地…

用SurfaceView实现落花动画效果

上篇文章 Android子线程真的不能刷新UI吗?(一)复现异常 中可以看出子线程更新main线程创建的View,会抛出异常。SurfaceView不依赖main线程,可以直接使用自己的线程控制绘制逻辑。具体代码怎么实现了? 这篇文章用Surfa…

C++入门基础篇(下)

目录 6.引用 6.1 引用的特性 6.2 const引用 7.指针和引用的关系 8.内联函数 9.nullptr 6.引用 引⽤不是新定义⼀个变量,⽽是给已存在变量取了⼀个别名,编译器不会为引⽤变量开辟内存空间, 它和它引⽤的变量共⽤同⼀块内存空间。比如&a…

Linux系统(Centos)下MySQL数据库中文乱码问题解决

问题描述:在进行数据库使用过程中,数据库里的数据中文都显示乱码。操作数据库的时候,会出现中文乱码问题。 解决方法如下: 第一步:打开虚拟机进入系统,启动MySQL。 第二步:连接登录MySQL输入…

2.5 C#视觉程序开发实例1----IO_Manager实现脉冲输出控制

2.5 C#视觉程序开发实例1----IO_Manager实现脉冲输出控制 1 目标效果视频 目标效果展示 IO_Manager 2 信号输出流程说明 为了防止线程不同步导致输出信号没有被输出, 尽量使用一个输出队列来进行输出的管理 3 IO_Manager中添加内容 3.0 添加两个类 1 Out_Sta…

idea中打开静态网页端口是63342而不是8080

问题: 安装了tomcat 并且也配置了环境,但是在tomcat下运行,总是在63342下面显示。这也就意味着,并没有运行到tomcat环境下。 找了好几个教程(中间还去学习了maven,因为跟的教程里面,没有maven,但…

C++ STL IO流介绍

目录 一:IO流的继承关系: 二:输入输出功能 1. 基本用法 2. 格式化输入 3.非格式化输入 4. 格式化输出 三:流 1. 字符流 2. 向字符流中写入数据 3. 从字符流中读出数据 4. 清空字符流 5.完整的例子 四:文件…

php快速入门

前言 php是一门脚本语言,可以访问服务器,对数据库增删查改(后台/后端语言) 后台语言:php,java,c,c,python等等 注意:php是操作服务器,不能直接在…

数据结构(3.8)——栈的应用

栈在括号匹配中的应用 流程图 代码 #include <stdio.h> #include <stdlib.h> #define MaxSize 10typedef struct {char data[MaxSize];int top; } SqStack;// 初始化栈 void InitStack(SqStack* S) {S->top -1; // 初始化栈顶指针 }// 判空 bool StackEmpty(…

设计模式探索:观察者模式

1. 观察者模式 1.1 什么是观察者模式 观察者模式用于建立一种对象与对象之间的依赖关系&#xff0c;当一个对象发生改变时将自动通知其他对象&#xff0c;其他对象会相应地作出反应。 在观察者模式中有如下角色&#xff1a; Subject&#xff08;抽象主题/被观察者&#xf…

C++第四弹 -- 类与对象(中上) (构造函数 析构函数 拷贝构造函数)

目录 前言构造函数1. 概念2. 特征 析构函数1. 概念2. 特征 拷贝构造函数1. 概念2. 特征 总结 前言 让我们一起揭开 C 对象生命周期管理的神秘面纱&#xff0c;掌握构造函数、析构函数和拷贝构造函数的精髓&#xff01; 博客主页: 酷酷学!!! 期待更多好文, 点击关注~ 构造函…

【Neo4j】实战 (数据库技术丛书)学习笔记

Neo4j实战 (数据库技术丛书) 第1章演示了应用Neo4j作为图形数据库对改进性能和扩展性的可能性, 也讨论了对图形建模的数据如何正好适应于Neo4j数据模型,现在到了该动 手实践的时间了。第一章 概述 Neo4j将数据作为顶点和边存储(或者用Neo4j术语,节点和关系存 储)。用户被定…

外卖商城平台小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;商家管理&#xff0c;骑手管理&#xff0c;商品类型管理&#xff0c;商品信息管理&#xff0c;订单信息管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;商品信息&#…

深入了解java锁升级可以应对各种疑难问题

对于java锁升级&#xff0c;很多人都停留在比较浅层的表面理解&#xff0c;一定程度下也许够用&#xff0c;但如果学习其中的细节&#xff0c;我们更好地理解多线程并发时各种疑难问题的应对方式&#xff01; 因此我将锁升级过程中可能涉及的大部分细节或者疑问都整合成了一篇…

后端之路——文件本地上传

一、基础原理 文件上传是一个很基础的知识点&#xff0c;尤其是本地上传&#xff0c;在现实开发基本都是云上传&#xff0c;但是作为一个基础要简单了解一下 首先前端我就不多讲解了&#xff0c;网页开发里用<form>表单可以上传文件&#xff0c;只需要加上这三属性&…

防火墙基础实验配置

一&#xff0c;实验拓扑 二&#xff0c;实验需求&#xff1a; 1.DMZ区内的服务器&#xff0c;办公区仅能在办公时间内&#xff08;9&#xff1a;00 - 18&#xff1a;00&#xff09;可以访问&#xff0c;生产区的设备全天可以访问 2.生产区不允许访问互联网&#xff0c;办公区…

如何批量更改很多个文件夹里的文件名中包含文件夹名?

&#x1f3c6;本文收录于《CSDN问答解惑-专业版》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收…