【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://xiahunao.cn/news/3227144.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

绝区玖--人工智能物料清单 (AI BOM)

前言 AI BOM 涵盖了从输入模型的数据到为模型提供支持的基础设施以及将 AI 从概念转化为生产的过程的一切。 但为什么我们需要人工智能物料清单?答案在于当今世界人工智能/Gen AI系统的复杂性和关键性: 透明度和可重复性:AI BOM 提供所有组件…

python怎么求因数

要想做到python语言求因数方法,首先要明白其中的原理: 1、对由123456789这九个数字组成的9位数进行分解质因数。 2、1234576982x3x3x7x13x23x29x113,所以他的值因数是113。 3、总共有362880种可能,从中找出值因数中最小的数字和…

动态规划算法专题二--路径问题

目录 专题二: 路径问题 题五 不同路径 1、算法解析 1、确定状态: 2、状态转移方程: 3、初始化: 4、填表顺序: 5、返回值: 2、代码 题六 不同路径II 1、算法解析 1、确定状态: 2、状态…

前端面试题(CSS篇六)

一、浏览器如何判断是否支持 webp 格式图片 (1)宽高判断法。通过创建image对象,将其src属性设置为webp格式的图片,然后在onload事件中获取图片的宽高,如果能够获取,则说明浏览器支持webp格式图片。如果不能…

Qt:13.多元素控件(QLinstWidget-用于显示项目列表的窗口部件、QTableWidget- 用于显示二维数据表)

目录 一、QLinstWidget-用于显示项目列表的窗口部件: 1.1QLinstWidget介绍: 1.2属性介绍: 1.3常用方法介绍: 1.4信号介绍: 1.5实例演示: 二、QTableWidget- 用于显示二维数据表: 2.1QTabl…

Vue学习笔记(小满zs)

本文章记录一下我的学习笔记,供复习参考。🏆 向大佬学习!!! ⭐小满zs Nodejs Nodejs 三层组成 libuv(处理事件循环、I/O操作) 第三方库(处理HTTP等) V8引擎&#xff08…

Windows10系统下mysql5.6的安装步骤

1.下载mysql 下载地址:https://downloads.mysql.com/archives/community/ 在这里我们下载zip的包 2.解压mysql包到指定目录 3. 添加my.ini文件 # For advice on how to change settings please see # http://dev.mysql.com/doc/refman/5.6/en/server-configurat…

【欧美高端NFT链游--大嘴怪/小黄人】链游

#游戏#链游 呆萌的小黄人出现在大嘴怪的地盘上会发生什么有趣的事情呢?#动画#游戏#小黄人 大嘴怪与小黑人之间起了冲突,大嘴怪爆发了,他决定要给小黑人们一点颜色瞧瞧,用自己的拳头,以及??嘴巴!大嘴怪有…

视频号的视频,一键就下载了,方法全在这儿了!

居然还有人不知道:视频号里面的视频是没有地址的,只能有微信自带的浏览器中打开。 所以很多人在视频号找到想要的素材,却无法下载,表示很苦恼。 几天每天都有人群里求助:“求好心人帮我下载一下这个视频!…

漏洞挖掘 | 记某证书站任意账号接管漏洞

下文中所述漏洞已修复 在前段时间的漏洞挖掘中,上了某证书站,打点的一处逻辑漏洞 访问某一站点,发现了一处登录页 点击登录按钮之后,发现该站点大概率是自写站点,存在逻辑漏洞的可能性大大增大,利用前期信…

产品软文应该怎么写,纯干货

产品软文是把一款产品的卖点很含蓄地表达在文章里面,通过特定的方式让这些枯燥的说明变得亲近人,以此传达一种价值观念,从而让人们对它产生一定的认知,能够潜移默化的感染着客户,可以提高产品和品牌的可见性和知名度。…

typora 两边太宽,设置宽度

步骤: 查看目前使用主题类型 文件 —> 偏好设置 —> 外观 —> 打开主题文件夹 修改对应的主题:max-width

ubuntu笔记本X86安装nomachine客户端

资源下载: 链接: link 一、首先下载文件 nomachine_8.2.3_4_x86_64.tar.gz到桌面。 二、打开终端,依次输入 进入root模式,需要输入密码,密码不可见。 sudu su复制nomachine_8.2.3_4_x86_64.tar.gz粘贴到/usr目录: cp -r nomachine_8.2.3_4_x86_64.tar.gz /usr进入

【后端开发实习】用MongoDB实现仓库管理的出库入库实战

用MongoDB实现仓库管理的出库入库 MongoDB什么是MongoDBMongoDB安装以及开始运行配置启动以及mongoshmongodb的基础使用命令启动和使用MongoDB服务数据库操作集合操作文档操作 项目部署在数据库中创建一张商品信息表提供信息表的增删改查操作接口 MongoDB 什么是MongoDB Mong…

‘wget‘ 不是内部或外部命令,也不是可运行的程序

在Windows环境下创建了虚拟环境并安装了wget包,但在使用该命令的时候仍然报错,‘wget’ 不是内部或外部命令,也不是可运行的程序 解决方案: 去官网下载对应位数的.exe文件,将其放在C:\Windows\System32目录下即可, 别下错版本&a…

C语言-预处理详解

文章目录 🎯引言👓预处理详解1.预定义符号1.1 __FILE__1.2 __LINE__1.3 __DATE__1.4 __TIME__1.5 __STDC__ 2.#define定义常量2.1 定义数值常量2.2 定义字符串常量 3.#define中使用参数3.1**使用示例**3.2注意事项 4.宏替换的规则5.宏函数和函数的对比5.…

windows远程连接virtualbox的ubuntu问题

一.安装vritualbox ubuntu,18、22版本比较稳定 1.推荐使用ubuntu22版本 2.ubuntu24对内存要求较高至少4G,时不时会死机,安装老是崩溃,恢复不了,如果电脑性能强悍那可以尝试。 3.ubuntu18 对vscode最高只能支持1.85.…

Spring中的工厂模式详解及应用示例

1. Spring中的BeanFactory BeanFactory是一个接口,表示它是一个工厂,负责生产和管理bean。在Spring中,BeanFactory是IOC容器的核心接口,定义了管理Bean的通用方法,如 getBean 和 containsBean。 BeanFactory与IOC容器…

海外视频媒体发布/发稿:如何在国外媒体以视频的形式宣发

1. 背景介绍 在如今数字化时代,每个国家都拥有着各自的视频媒体平台,而主流媒体也都纷纷加入了视频发布的行列。视频媒体的宣发形式主要包括油管Youtube等视频分享平台,以及图文配合的发布方式。通过在视频中夹带链接,媒体可以以…

C++ 宏和内联、范围for、nullptr

C 宏函数和内联函数、范围for、nullptr 宏函数和内联函数 ​ 函数重载中提到过,一个程序编译需要经过四个阶段,第一个阶段预处理中有一个操作是宏替换。由于是替换,所以宏不建立栈帧,且没有数据类型的限制,能够提高我…