You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用riscv-none-embed-gcc时如何将main内局部变量分配至数据内存

预期实现效果参考图

问题背景

预期实现效果:系统存在两块独立内存,分别为程序内存与数据内存,访问数据时需从数据内存中读取对应值。
测试所用C代码如下:

#define XRAM0 0x4000      // REG I/O

int b=0x35;

void main ()
{
    
    int a=0x55;        // 希望GCC编译器将0x55初始值写入mem_data区域
    
    int *address;
    
    address = XRAM0;
    address[0]=a;      // 但GCC始终从DBus读取a变量(该区域未初始化,不存在0x55值)
    address[1]=b;
}

代码包含全局变量b与局部变量a,当前使用riscv-none-embed-gcc编译后,始终从DBus读取a变量,但对应区域未完成初始化,无法读取到0x55值,目标是让编译器将a的初始值0x55预先写入mem_data区域。
本次编译通过ld链接脚本配置内存地址映射,所用编译命令如下:

riscv-none-embed-gcc -lc -nostdlib  -O3 -march=rv32im  -T test.ld TEST.c -o TEST.o -lg -lgloss -mcmodel=medlow 
riscv-none-embed-objcopy -O verilog "TEST.o"  "TEST.vn"
riscv-none-embed-objdump --disassemble-all -S -d TEST.o > TEST.lst 

第一版链接脚本与编译结果

第一版链接脚本已配置程序内存与数据内存起始地址,指定数据段存放至数据内存,内容如下:

OUTPUT_ARCH( "riscv" )

MEMORY {
  mem_text (rx) : ORIGIN = 0x00000000, LENGTH = 0x1000000   // 程序内存起始地址
  mem_data (rw) : ORIGIN = 0x20000000, LENGTH = 0x1000000   // 数据内存起始地址
}

SECTIONS
{
  . = 0x00000000;
 
  .text : { *(.text) } > mem_text  // 代码段放入程序内存

  . = 0x20000000;
  .data : {
    *(.sdata)
  } > mem_data   // 数据段放入数据内存
}

编译后全局变量b已存放至mem_data区域,但main函数内的局部变量a未存入该区域,反汇编结果如下:

TEST.o:     file format elf32-littleriscv


Disassembly of section .text.startup:

00000000 <main>:
   0:   000047b7            lui a5,0x4
   4:   05500713            li  a4,85
   8:   00e7a023            sw  a4,0(a5) # 4000 <main+0x4000>
   c:   20000737            lui a4,0x20000
  10:   00072703            lw  a4,0(a4) # 20000000 <b>
  14:   00e7a223            sw  a4,4(a5)
  18:   00008067            ret

Disassembly of section .data:

20000000 <b>:
20000000:   0035                    c.nop   13   // b变量位于mem_data区域,a变量不在该区域

转换为机器码后结果一致,仅b变量位于mem_data区域,a变量未存入:

@00000000
B7 47 00 00 13 07 50 05 23 A0 E7 00 37 07 00 20 
03 27 07 00 23 A2 E7 00 67 80 00 00 
@20000000
35 00 00 00   // b变量位于mem_data区域,a变量不在该区域

第二版链接脚本与编译结果

修改链接脚本后机器码生成结果无变化,修改后的链接脚本如下:

OUTPUT_ARCH( "riscv" )

MEMORY {
  mem_text (rx) : ORIGIN = 0x00000000, LENGTH = 0x1000000   
  mem_data (rw) : ORIGIN = 0x20000000, LENGTH = 0x1000000   
}

SECTIONS
{
  . = 0x00000000;
 
  .text : { *(.text) } > mem_text  

  . = 0x20000000;
  .data : {
    *(.sdata)
  } > mem_data at > mem_text
}

修改后反汇编结果如下:

TEST.o:     file format elf32-littleriscv


Disassembly of section .text.startup:

00000000 <main>:
   0:   000047b7            lui a5,0x4
   4:   05500713            li  a4,85
   8:   00e7a023            sw  a4,0(a5) # 4000 <main+0x4000>
   c:   20000737            lui a4,0x20000
  10:   00072703            lw  a4,0(a4) # 20000000 <b>
  14:   00e7a223            sw  a4,4(a5)
  18:   00008067            ret

Disassembly of section .data:

20000000 <b>:
20000000:   0035                    c.nop   13

对应生成的机器码如下:

@00000000
B7 47 00 00 13 07 50 05 23 A0 E7 00 37 07 00 20 
03 27 07 00 23 A2 E7 00 67 80 00 00 
@0000001C
35 00 00 00 
问题原因

改链接脚本无效的核心原因非常明确:开启-O3优化的情况下,普通的非static、非volatile局部变量不会被分配内存空间,编译器会直接把它的常量值硬编码成指令里的立即数。
看反汇编代码里的li a4,85,85就是0x55,编译器已经把a的值直接嵌到指令里了,从头到尾没给a分配数据段或者栈上的内存地址,这种情况下不管怎么改链接脚本,都不可能把不存在的内存变量放到mem_data区域。
另外链接脚本本身也有配置缺陷:.data段只匹配了.sdata段,普通.data段、.bss段、栈空间都没有正确映射到mem_data,就算变量被分配到数据段,也可能因为匹配规则遗漏没有放到目标内存区域。

解决方案

根据需求选以下任意一种方案即可:

  • 方案1:把局部变量a改为静态存储周期。要么把a定义成全局变量,要么给局部变量a加static修饰,这样a会被编译器分配到.data段,只要链接脚本正确配置.data段到mem_data,0x55的初始值就会被预存到数据内存。修改后的代码示例:
#define XRAM0 0x4000      // REG I/O

int b=0x35;
static int a = 0x55; // static修饰后进入.data段

void main ()
{
    int *address;
    address = XRAM0;
    address[0] = a;
    address[1] = b;
}
  • 方案2:用volatile修饰a同时配置栈空间到mem_data。给a加volatile关键字可以强制编译器禁止常量折叠优化,必须给a分配内存地址。注意这种方式a默认分配在栈上,需要同时在链接脚本里把栈指针指向mem_data地址范围,否则a还是会落在非目标内存区域。示例代码:
#define XRAM0 0x4000      // REG I/O

int b=0x35;

void main ()
{
    volatile int a = 0x55; // 强制分配内存,禁止优化为立即数
    int *address;
    address = XRAM0;
    address[0] = a;
    address[1] = b;
}

补全链接脚本里.data段的匹配规则,修正后的.data段配置参考:

.data : {
    *(.sdata)
    *(.sdata.*)
    *(.data)
    *(.data.*)
    . = ALIGN(4);
} > mem_data

如果硬件是哈佛架构,程序内存和数据内存完全独立、地址总线分开,还需要补充一段启动代码:上电后把存储在程序内存里的.data段初始值拷贝到mem_data对应的地址,否则就算链接脚本配置正确,数据内存上电时是随机值,也读不到预存的0x55和0x35初始值。第二版链接脚本里写的> mem_data at > mem_text就是把.data的加载地址放在程序内存、运行地址放在数据内存,这种配置必须搭配上述拷贝启动代码才能正常工作。


内容的提问来源于stack exchange,提问作者kostya

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 20:55:00