在ARM7TDMI上获取参数地址时,GCC是否中断?
我的C代码段采用一个参数的地址,并将其存储在易失性存储器位置(预处理代码):
void foo(unsigned int x) { *(volatile unsigned int*)(0x4000000 + 0xd4) = (unsigned int)(&x); } int main() { foo(1); while(1); }
我使用SVN版本的GCC来编译此代码。在函数foo的末尾,我希望将值1存储在堆栈中,并在地址0x40000d4处指向该值的地址。当我使用标志-O0进行优化时,我得到了预期的ARM7TMDI程序集输出(为方便起见进行了评论):
.align 2 .global foo .type foo, %function foo: @ Function supports interworking. @ args = 0, pretend = 0, frame = 8 @ frame_needed = 0, uses_anonymous_args = 0 @ link register save eliminated. sub sp, sp, #8 str r0, [sp, #4] @ 3. Store the argument on the stack mov r3, #67108864 add r3, r3, #212 add r2, sp, #4 @ 4. Address of the stack variable str r2, [r3, #0] @ 5. Store the address at 0x40000d4 add sp, sp, #8 bx lr .size foo, .-foo .align 2 .global main .type main, %function main: @ Function supports interworking. @ args = 0, pretend = 0, frame = 0 @ frame_needed = 0, uses_anonymous_args = 0 stmfd sp!, {r4, lr} mov r0, #1 @ 1. Pass the argument in register 0 bl foo @ 2. Call function foo .L4: b .L4 .size main, .-main .ident "GCC: (GNU) 4.4.0 20080820 (experimental)"
显然,它首先将参数存储在堆栈上,然后从那里将其存储在" 0x40000d4"处。当我使用-O1
进行优化编译时,得到了一些意想不到的东西:
.align 2 .global foo .type foo, %function foo: @ Function supports interworking. @ args = 0, pretend = 0, frame = 8 @ frame_needed = 0, uses_anonymous_args = 0 @ link register save eliminated. sub sp, sp, #8 mov r2, #67108864 add r3, sp, #4 @ 3. Address of *something* on the stack str r3, [r2, #212] @ 4. Store the address at 0x40000d4 add sp, sp, #8 bx lr .size foo, .-foo .align 2 .global main .type main, %function main: @ Function supports interworking. @ args = 0, pretend = 0, frame = 0 @ frame_needed = 0, uses_anonymous_args = 0 stmfd sp!, {r4, lr} mov r0, #1 @ 1. Pass the argument in register 0 bl foo @ 2. Call function foo .L4: b .L4 .size main, .-main .ident "GCC: (GNU) 4.4.0 20080820 (experimental)"
这次,即使堆栈中的某些内容仍存储在" 0x40000d4",该参数也不会存储在堆栈中。
这只是预期/未定义的行为吗?我做错了什么还是实际上发现了编译器错误?
解决方案
回答
Is this just expected/undefined behaviour? Have I done something wrong or have I in fact found a Compiler Bug??
只是优化选项可以产生可能无法正常工作的奇怪代码的定义行为没有bug :)
编辑:
如果我们认为自己在GCC中发现了一个错误,则很高兴我们投递到邮件列表中,但总的来说,他们发现知识中存在一些漏洞,那就是无情地责备和嘲笑:(
在这种情况下,我认为可能是-O选项尝试尝试使代码中断的快捷方式需要解决。
回答
我很想知道现在是否可以找到引用,但是我99%的确定我们总是应该能够接受参数的地址,这取决于编译器来完善调用约定的细节。 ,注册使用情况等。
确实,我会认为这是一个普遍的要求,因此很难看到其中存在普遍问题,我想知道这是否是动摇的指针使优化变得不安。
就个人而言,我可能会尝试这样做以查看其是否编译更好:
void foo(unsigned int x) { volatile unsigned int* pArg = &x; *(volatile unsigned int*)(0x4000000 + 0xd4) = (unsigned int)pArg; }
回答
火花写道
If you think you have found a bug in GCC the mailing lists will be glad you dropped by but generally they find some hole in your knowledge is to blame and mock mercilessly :(
我想先去这里碰运气,然后再转到GCC邮件列表显示我的无能:)
亚当·戴维斯(Adam Davis)写道
Out of curiosity, what are you trying to accomplish?
我正在尝试Game Boy Advance的开发。我正在阅读有关其DMA系统的文章,并通过创建单色图块位图对其进行了实验。想法是将索引颜色作为参数传递给函数,该函数将使用DMA填充该颜色的图块。 DMA传输的源地址存储在" 0x40000d4"。
威尔·迪恩写道
Personally, I might do try this to see if it compiled better: void foo(unsigned int x) { volatile unsigned int* pArg = &x; *(volatile unsigned int*)(0x4000000 + 0xd4) = (unsigned int)pArg; }
我已经在问题中发布了-O0
,以及-O1
已针对完全相同的-O1
程序集进行了优化。
回答
因此,我们要将本地堆栈变量的地址放入要使用的DMA控制器中,然后从可用堆栈变量的函数返回?
虽然这可能与main()示例一起使用(由于我们不再在堆栈上进行写操作),但以后在"真实"程序中将无法使用,因为在另一个函数被调用之前或者访问DMA时,该值将被覆盖调用,然后再次使用堆栈。
我们需要具有一种结构或者一个全局变量,我们可以在DMA访问它时使用它来存储此值,否则它将被破坏!
-亚当
回答
不是答案,只是一些更多的信息给我们。
我的日常工作是运行3.4.5 20051201(Red Hat 3.4.5-2)。
我们还注意到某些代码(我无法在此处发布)在以下情况下停止工作:
我们添加-O1标志。我们的解决方案是暂时删除该标志:(
回答
我确实认为编译器没有错,尽管这是一个奇怪的情况。
从代码分析的角度来看,它可以看到我们存储了变量的地址,但是该地址从未被取消引用,并且我们也不会跳到函数外部,而可以使用可以使用我们存储的地址的外部代码。当退出函数时,堆栈的地址现在被视为伪造的,因为它的变量地址不再存在。
" volatile"关键字在C语言中实际上并没有多大作用,特别是对于多线程或者硬件。它只是告诉编译器必须进行访问。但是,由于没有根据数据流使用x值的用户,因此没有理由将" 1"存储在堆栈中。
如果你写的话,它可能会工作
void foo(unsigned int x) { volatile int y = x; *(volatile unsigned int*)(0x4000000 + 0xd4) = (unsigned int)(&y); }
尽管它仍然可能是非法代码,但由于foo返回后立即将y的地址视为无效,但是DMA系统的本质是独立于程序流来引用该位置。
回答
总的来说,这是一个有效的优化。
如果我们想深入了解它,可以使用-da进行编译
这将生成一个.c.Number.Passname,我们可以在其中查看rtl(gcc中的中间表示形式)。在那里,我们可以看到哪个通行证可以进行哪种优化(也许可以禁用那些我们不想拥有的优化)
回答
从foo()返回后,x消失了,指向它的任何指针都是无效的。随后使用这样的指针会导致C标准将其称为"未定义行为",这意味着绝对可以允许编译器假定我们不会取消引用它,或者(如果我们仍然坚持这样做,则)不必生成代码像我们可能期望的那样远程执行任何操作。如果我们希望指向x
的指针在foo()返回后保持有效,则一定不要在foo的堆栈上分配x
,即-即使我们知道原理上也没有任何理由-因为在C语言中是不允许的,无论它多久执行一次我们期望的操作。
最简单的解决方案可能是使" x"成为" main()"中的局部变量(或者任何其他具有足够长寿命的函数),然后将地址传递给foo。我们还可以将x设置为全局变量,或者使用malloc()在堆上分配它,或者以某种更奇特的方式为其留出内存。我们甚至可以尝试以某种(希望)更可移植的方式找出堆栈顶部的位置,并将数据显式存储在堆栈的某些部分,如果我们确定不需要其他任何东西,并且我们坚信这是我们真正需要做的。但是,正如我们所发现的那样,我们一直在使用的方法不够可靠。
回答
需要注意的一件事是,根据标准,强制类型转换是r值。 GCC曾经允许使用它,但是在最近的版本中已经成为了一些标准的工具。
我不知道这是否会有所作为,但我们应该尝试以下操作:
void foo(unsigned int x) { volatile unsigned int* ptr = (unsigned int*)(0x4000000 + 0xd4); *ptr = (unsigned int)(&x); } int main() { foo(1); while(1); }
另外,我怀疑我们是否打算这样做,但是我们正在存储函数local x(它是我们传递的int的副本)的地址。我们可能希望使foo带有" unsigned int *"并传递我们真正想要存储的地址。
所以我觉得一个更合适的解决方案是:
void foo(unsigned int *x) { volatile unsigned int* ptr = (unsigned int*)(0x4000000 + 0xd4); *ptr = (unsigned int)(x); } int main() { int x = 1; foo(&x); while(1); }
编辑:最后,如果代码因优化而中断,通常通常表明代码做错了什么。
回答
我认为Even T.都有答案。我们传入了一个变量,但是不能获取该变量在函数内部的地址,但是可以获取该变量的副本的地址,尽管该变量通常是寄存器,所以它没有地址。一旦我们将该函数全部删除,调用函数就会丢失它。如果我们在函数中需要地址,则必须通过引用传递而不是通过值传递,请发送地址。在我看来,该错误在代码中,而不是gcc。
顺便说一句,使用*(volatile blah *)0xabcd或者其他任何方法来尝试对寄存器进行编程最终都会咬你。 gcc和大多数其他编译器具有这种不可思议的方式,可以准确地知道最糟糕的罢工时间。
说你改变这一天
*(volatile unsigned int *)0x12345 = someuintvariable;
到
*(volatile unsigned int *)0x12345 = 0x12;
一个好的编译器将意识到我们只存储8位,并且没有理由为此浪费32位存储,具体取决于我们指定的体系结构或者该编译器当日的默认体系结构,因此在其权限范围内将其优化为strb而不是str。
在被gcc和其他人烧掉数十次之后,我不得不提出这个问题:
.globl PUT32 PUT32: str r1,[r0] bx lr PUT32(0x12345,0x12);
花费了一些额外的时钟周期,但是我的代码昨天,今天和今天继续工作,明天将与任何优化标志一起工作。不必重新访问旧代码并整夜安眠,这值得在这里和那里多几个时钟周期。
同样,如果在为发布而不是为调试而编译时代码中断,这也意味着它很可能是代码中的错误。