GNU ld(链接器脚本)手册章节3.5.5 Source Code Reference有一些关于如何在C源代码中访问链接器脚本“变量”(实际上只是整数地址)的重要信息。我用过这个信息。广泛使用链接器脚本变量,我在这里写了这个答案:How to get value of variable defined in ld linker script from C。
但是,很容易做错,并且错误地尝试访问链接器脚本变量的值(错误地)而不是其地址,因为这有点深奥。手册(上面的链接)说:
这意味着您无法访问链接器脚本定义的符号值 - 它没有值 - 您所能做的就是访问链接器脚本定义的符号的地址。
因此,当您在源代码中使用链接器脚本定义符号时,应始终使用符号的地址,而不要尝试使用其值。
问题:那么,如果您尝试访问链接器脚本变量的值,这是“未定义的行为”吗?
想象一下在链接器脚本中(例如:STM32F103RBTx_FLASH.ld)你有:
/* Specify the memory areas */
MEMORY
{
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 128K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K
}
/* Some custom variables (addresses) I intend to access from my C source code */
__flash_start__ = ORIGIN(FLASH);
__flash_end__ = ORIGIN(FLASH) + LENGTH(FLASH);
__ram_start__ = ORIGIN(RAM);
__ram_end__ = ORIGIIN(RAM) + LENGTH(RAM);
在您的C源代码中,您可以:
// 1. correct way A:
extern uint32_t __flash_start__;
printf("__flash_start__ addr = 0x%lX\n", (uint32_t)&__flash_start__);
// OR 2. correct way B (my preferred approach):
extern uint32_t __flash_start__[]; // not a true array; [] is required to access linker script variables (addresses) as though they were normal variables
printf("__flash_start__ addr = 0x%lX\n", (uint32_t)__flash_start__);
// OR 3. COMPLETELY WRONG WAY TO DO IT!
// - IS THIS UNDEFINED BEHAVIOR?
extern uint32_t __flash_start__;
printf("__flash_start__ addr = 0x%lX\n", __flash_start__);
(这是实际输出:它实际上由STM32 mcu编译,运行和打印):
__flash_start__ addr = 0x8000000
__flash_start__ addr = 0x8000000
__flash_start__ addr = 0x20080000
<==注意我上面说过:这个是完全错误的(即使它编译并运行)!回复@Eric Postpischil的第一条评论:
C标准没有对链接器脚本符号进行任何定义。任何行为规范都取决于GNU工具。也就是说,如果链接描述符号在内存中标识了存储某个有效对象的位置,我希望访问该对象的值才能工作,如果它是以适当的类型访问的话。假设flash_start通常是可访问的内存,除了系统对flash_start的任何要求之外,理论上你可以将uint32_t(使用适当的输入连接到链接器)然后通过flash_start访问它。
是的,但这不是我的问题。我不确定你是否接受了我的问题的微妙之处。看一下我提供的例子。确实你可以很好地访问这个位置,但要确保你理解你是如何做到的,然后我的问题就会变得明显。特别注意上面的例子3,这是错误的,即使对于C程序员它看起来是正确的。要阅读uint32_t
,例如,在__flash_start__
,你会这样做:
extern uint32_t __flash_start__;
uint32_t u32 = *((uint32_t *)&__flash_start__); // correct, even though it *looks like* you're taking the address (&) of an address (__flash_start__)
或这个:
extern uint32_t __flash_start__[];
uint32_t u32 = *((uint32_t *)__flash_start__); // also correct, and my preferred way of doing it because it looks more correct to the trained "C-programmer" eye
但绝大多数不是这样的:
extern uint32_t __flash_start__;
uint32_t u32 = __flash_start__; // incorrect; <==UPDATE: THIS IS ALSO CORRECT! (and more straight-forward too, actually; see comment discussion under this question)
而不是这个:
extern uint32_t __flash_start__;
uint32_t u32 = *((uint32_t *)__flash_start__); // incorrect, but *looks* right
我在问题中说:
// 1. correct way A:
extern uint32_t __flash_start__;
printf("__flash_start__ addr = 0x%lX\n", (uint32_t)&__flash_start__);
// OR 2. correct way B (my preferred approach):
extern uint32_t __flash_start__[]; // not a true array; [] is required to access linker script variables (addresses) as though they were normal variables
printf("__flash_start__ addr = 0x%lX\n", (uint32_t)__flash_start__);
// OR 3. COMPLETELY WRONG WAY TO DO IT!
// - IS THIS UNDEFINED BEHAVIOR?
extern uint32_t __flash_start__;
printf("__flash_start__ addr = 0x%lX\n", __flash_start__);
(请参阅问题讨论我是如何做到的)。
具体看上面的#3:
好吧,实际上,如果你的目标是读取__flash_start__
的地址,在这种情况下是0x8000000
,那么是的,这是完全错误的。但是,它不是未定义的行为!实际上它正在做的是将该地址(0x8000000
)的内容(值)读作uint32_t
类型。换句话说,它只是读取FLASH部分的前4个字节,并将它们解释为uint32_t
。在这种情况下,内容(此地址的uint32_t
值)恰好是0x20080000
。
为了进一步证明这一点,以下内容完全相同:
// Read the actual *contents* of the __flash_start__ address as a 4-byte value!
// The 2 techniques should be the same.
extern uint32_t __flash_start__;
uint32_t u32_1 = __flash_start__;
uint32_t u32_2 = *((uint32_t *)&__flash_start__);
printf("u32_1 = 0x%lX\n", u32_1);
printf("u32_2 = 0x%lX\n", u32_2);
输出是:
u32_1 = 0x20080000
u32_2 = 0x20080000
注意它们会产生相同的结果。它们每个都产生一个有效的uint32_t
类型值,存储在地址0x8000000
。
然而,事实证明,上面显示的u32_1
技术是一种更直接,更直接的方式来读取价值,而且,这并非未定义的行为。相反,它正确地读取该地址的值(内容)。
我好像在说话。无论如何,心灵都被吹了,但我现在明白了。在我应该使用上面显示的u32_2
技术之前我确信,但事实证明它们都很好,再次,u32_1
技术显然更直接(我再次在圈子里说话)。 :)
干杯。
0x20080000
value stored right at the start of my FLASH memory come from?还有一点点花絮。我实际上在STM32F777 mcu上运行了这个测试代码,它有512KiB的RAM。由于RAM从地址0x20000000开始,这意味着0x20000000 + 512K = 0x20080000。这恰好也是地址零处RAM的内容,因为Programming Manual PM0253 Rev 4,pg。 42,“图10.向量表”表示向量表的前4个字节包含“初始SP [堆栈指针]值”。看这里:
我知道向量表就位于程序存储器的开头,它位于Flash中,因此这意味着0x20080000是我的初始堆栈指针值。这是有道理的,因为Reset_Handler
是程序的开始(顺便说一下它的向量恰好是向量表开头的第二个4字节值),并且它做的第一件事,如图所示在我的“startup_stm32f777xx.s”启动程序集文件中,将堆栈指针(sp)设置为_estack
:
Reset_Handler:
ldr sp, =_estack /* set stack pointer */
此外,_estack
在我的链接器脚本中定义如下:
/* Highest address of the user mode stack */
_estack = ORIGIN(RAM) + LENGTH(RAM); /* end of RAM */
所以你有它!我的向量表中的第一个4字节值,就在Flash的开头,被设置为初始堆栈指针值,在我的链接器脚本文件中定义为_estack
,而_estack
是我的末尾的地址RAM,即0x20000000 + 512K = 0x20080000。所以,这一切都有道理!我刚刚证明我读了正确的价值!