当用gcc编译时,外部变量总是需要是volatile的吗?

nwnhqdif  于 2022-11-13  发布在  其他
关注(0)|答案(1)|浏览(148)

我试着设置一个外部变量,然后获取它的值,但是我得到的值不正确。当用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

这是不正确的。

bvk5enib

bvk5enib1#

计算&ex_var+16不是由C标准定义的(因为它只定义了对象内的指针运算,包括指向其末尾以外的地址),并且赋值*pos++ = 1不是由C标准定义的(因为,就标准而言,pos不指向对象)。当代码路径上存在C标准未定义的行为时,该标准没有定义代码路径上的任何行为。
您可以通过将ex_var声明为一个大小未知的数组,在编译器能够看到的范围内定义该行为,这样,如果该翻译单元与另一个将ex_var定义为足够大的数组的翻译单元相链接,则可以定义地址计算和赋值:

extern unsigned int ex_var[];

int main ()
{
    ex_var[0] = 56326;

    unsigned int *pos = ex_var+16;

    for (int i = 0; i < 6; i++ )
    {
        *pos++ = 1;
    }
    *(volatile unsigned int*)(0x00100000) = ex_var[0];
}

(Note *(volatile unsigned int*)(0x00100000) =仍然没有被C标准定义,但是GCC是为裸机环境中的某些用途而设计的,并且似乎可以与此配合使用。可能需要额外的编译开关来确保它是为GCC的目的而定义的。)
这会产生设定ex_var[0]的组件,并在0x00100000的指派中使用它:

main:
        mov     DWORD PTR ex_var[rip], 56326
…
        mov     eax, DWORD PTR ex_var[rip]
        mov     DWORD PTR ds:1048576, eax
        mov     eax, 0
        ret

相关问题