我试着设置一个外部变量,然后获取它的值,但是我得到的值不正确。当用gcc编译时,外部变量总是需要是volatile
吗?
代码如下所示(更新了完整代码,将之前对存储器地址0x 00100000的访问更改为another
变量):
main.c
extern unsigned int ex_var;
extern unsigned int another;
int main ()
{
ex_var = 56326;
unsigned int *pos=&ex_var+16;
for (int i = 0; i < 6; i++ )
{
*pos++ = 1;
}
another = ex_var;
}
another.c
unsigned int ex_var; // the address of this variable is set to right before valid_address
unsigned int valid_address[1024]; // to indicate valid memory address
unsigned int another;
并且设置为another
的值不是56326。
注意:another.c似乎很奇怪,表示ex_var
之后的内存区域是有效的,实际在bear metal上运行的例子请参考this post。
下面是main.c的反汇编。它是使用x86-64 gcc 12.2和**-O 1**选项编译的:
main:
mov eax, OFFSET FLAT:ex_var+64
.L2:
add rax, 4
mov DWORD PTR [rax-4], 1
cmp rax, OFFSET FLAT:ex_var+88
jne .L2
mov eax, DWORD PTR ex_var[rip]
mov DWORD PTR another[rip], eax
mov eax, 0
ret
可以发现,用于设置外部变量ex_var
的代码被优化掉了。
我试了几个版本的gcc,包括x86-64 gcc,x86 gcc,arm 64 gcc,arm gcc,似乎所有测试的gcc版本8.x以上都有这个问题,注意需要优化选项-O 1或更高版本才能重现这个问题。
该代码位于this link at Compiler Explorer。
更新日期:
上述代码中的此错误与外部引用无关。
下面是另一个有相同bug的示例代码。注意它应该用-O 1或更高版本编译。你可以试试at Compiler Explorer。
#include <stdio.h>
unsigned int var;
// In embedded environment, LD files can be used to make valid_address stores right after var
volatile unsigned int valid_address[1024];
int main ()
{
var = 56326;
unsigned int *ttb=&var;
ttb += 16;
for (int i = 0; i < 8; i++ )
{
*ttb++ = 1;
}
valid_address[0] = var;
printf("Value is: %d", valid_address[0]);
}
如果您使用gcc编译此代码(如
gcc -O1 main1.c
并执行此代码,则可能会得到以下输出:
Value is: 0
这是不正确的。
1条答案
按热度按时间bvk5enib1#
计算
&ex_var+16
不是由C标准定义的(因为它只定义了对象内的指针运算,包括指向其末尾以外的地址),并且赋值*pos++ = 1
不是由C标准定义的(因为,就标准而言,pos
不指向对象)。当代码路径上存在C标准未定义的行为时,该标准没有定义代码路径上的任何行为。您可以通过将
ex_var
声明为一个大小未知的数组,在编译器能够看到的范围内定义该行为,这样,如果该翻译单元与另一个将ex_var
定义为足够大的数组的翻译单元相链接,则可以定义地址计算和赋值:(Note
*(volatile unsigned int*)(0x00100000) =
仍然没有被C标准定义,但是GCC是为裸机环境中的某些用途而设计的,并且似乎可以与此配合使用。可能需要额外的编译开关来确保它是为GCC的目的而定义的。)这会产生设定
ex_var[0]
的组件,并在0x00100000
的指派中使用它: