如何将变量放在内存中的给定绝对地址(使用GCC)

Bas*_*ijk 25 c variables gcc arm keil

RealView ARM C编译器支持使用变量属性将变量放在给定的内存地址at(address):

int var __attribute__((at(0x40001000)));
var = 4;   // changes the memory located at 0x40001000
Run Code Online (Sandbox Code Playgroud)

GCC是否具有类似的变量属性?

Pro*_*ica 22

我不知道,但你可以轻松地创建一个这样的解决方法:

int *var = (int*)0x40001000;
*var = 4;
Run Code Online (Sandbox Code Playgroud)

它不是完全相同的东西,但在大多数情况下是一个完美的替代品.它适用于任何编译器,而不仅仅是GCC.

如果您使用GCC,我假设您也使用GNU ld(当然,这不是确定的)并且ld支持将变量放在任何您想要的位置.

我想让联系人做这项工作很常见.

受@rib的回答启发,我将补充一点,如果绝对地址是针对某些控制寄存器的,我会添加volatile到指针定义中.如果它只是RAM,那没关系.

  • @JeremyP; 这或多或少是我的观点,问题并没有说明是否可以通过这种方式访问​​内存,或者是否需要编译器采取某些操作来实现它. (3认同)
  • @roe:这是一个相当标准的技巧,可用于具有固定内存映射控制寄存器的硬件的设备驱动程序.在标准用户应用程序中,它没有任何实用程序,无论我能想到什么. (2认同)
  • @JeremyP,在嵌入式设备中,尤其是那些没有 MMU 的设备中,让“用户应用程序”访问硬件是很常见的。 (2认同)
  • 我会将指针的类型更改为`int*const`,因此编译器可以优化取消引用. (2认同)

Tho*_*son 15

您可以使用section属性和ld 链接描述文件来定义该节的所需地址.这可能比您的替代方案更麻烦,但它是一种选择.

  • 请注意,此方法将*实际为变量保留*空间,而不是简单地假设它存在于指定的地址.在许多情况下,这就是你想要的. (7认同)
  • 不幸的是,有时工具试图通过隐藏/自动生成链接器脚本来使事情变得更简单,因此很难修改它(我正在看你的 Arduino 和 ATMEL Studio)。我很想找到一种方法将变量锚定到固定地址,并让它仅使用代码在现有段中分配空间。:/ (2认同)

小智 9

这是一种实际上在内存中的固定地址保留空间而无需编辑链接器文件的解决方案:

    extern const uint8_t dev_serial[12];
    asm(".equ dev_serial, 0x1FFFF7E8");
/* or    asm("dev_serial = 0x1FFFF7E8"); */
    ...
        
    for (i = 0 ; i < sizeof(dev_serial); i++)
        printf((char *)"%02x ", dev_serial[i]);
Run Code Online (Sandbox Code Playgroud)

  • 这实际上确实添加了其他答案没有添加的内容。通过使用这种方法,您可以定位固定长度的数组,同时仍然允许“sizeof()”获取数组大小。 (3认同)
  • 虽然此代码片段可以解决问题,但[包括解释](https://meta.stackexchange.com/questions/114762/explaining-entirely-code-based-answers)确实有助于提高帖子的质量。请记住,您是在为将来的读者回答问题,而那些人可能不知道您建议代码的原因。还请尽量不要在代码中添加解释性注释,这会降低代码和解释的可读性! (2认同)

小智 7

您已回答了问题,在上面的链接中指出:

使用GNU GCC编译器,您可以仅使用指针定义来访问绝对内存位置.例如:

#define IOPIN0         (*((volatile unsigned long *) 0xE0028000))
IOPIN0 = 0x4;
Run Code Online (Sandbox Code Playgroud)

顺便说一句http://gcc.gnu.org/onlinedocs/gcc-4.5.0/gcc/Variable-Attributes.html#Variable%20Attributes


Cir*_*四事件 5

最小的可运行链接描述文件示例

在以下网址中提到了该技​​术:https//stackoverflow.com/a/4081574/895245,但现在我将提供一个具体示例。

main.c

#include <stdio.h>

int myvar __attribute__((section(".mySection"))) = 0x9ABCDEF0;

int main(void) {
    printf("adr %p\n", (void*)&myvar);
    printf("val 0x%x\n", myvar);
    myvar = 0;
    printf("val 0x%x\n", myvar);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

链接

SECTIONS
{
  .mySegment 0x12345678 : {KEEP(*(.mySection))}
}
Run Code Online (Sandbox Code Playgroud)

GitHub上游

编译并运行:

SECTIONS
{
  .mySegment 0x12345678 : {KEEP(*(.mySection))}
}
Run Code Online (Sandbox Code Playgroud)

输出:

adr 0x12345678
val 0x9abcdef0
val 0x0
Run Code Online (Sandbox Code Playgroud)

因此,我们看到它已放置在所需的地址。

我在GCC手册中找不到该文档的记录位置,但语法如下:

gcc link.ld main.c
Run Code Online (Sandbox Code Playgroud)

似乎将给定的链接脚本附加到将要使用的默认脚本中。

-fno-pie -no-pie是必需的,因为Ubuntu工具链现在已配置为默认情况下生成PIE可执行文件,这导致Linux内核每次都将可执行文件放在不同的地址上,这与我们的实验相混淆。另请参见:gcc和ld中与位置无关的可执行文件的-fPIE选项是什么?

TODO:编译产生警告:

/usr/bin/x86_64-linux-gnu-ld: warning: link.ld contains output sections; did you forget -T?
Run Code Online (Sandbox Code Playgroud)

难道我做错了什么?如何摆脱它?另请参见:如何消除警告:link.res包含输出部分;你忘了-T吗?

在Ubuntu 18.10,GCC 8.2.0上进行了测试。