我正在开发一个简单的游戏男孩高级光盘,并试图理解为什么以下代码与GCC -O0选项一起工作,但在-O1或更高版本时崩溃(白色模拟器屏幕):

int main () {
    // Set video mode 3 and background 2
    *(unsigned int*)0x04000000 = 0x0403;

    int x;
    for(x = 0; x < 1; x++){
        // Set a single pixel at position (120, 80) in VRAM to red
        ((unsigned short*)0x06000000)[120+80*240] = 0x001F;
    };

    while(1);

    return 0;
}

循环显然是不必要的,但我使用它创建了一个崩溃行为的最小示例.在没有循环的情况下,无论优化级别如何,代码都能正常工作.有了循环,它在-O1或更高的地方崩溃,但在-O0上可以工作.

无论我是否在循环体中实际使用x(例如,使用x来计算像素位置),都会发生相同的行为.就我所知,每当我try 在循环中进行这种类型的直接内存修改时,我都会在更高的优化级别上得到 destruct .

这里发生了什么事?是什么优化破解了代码?这是否表明我做事的方式有什么问题?谢谢你的帮助!

以下是更多细节:

  • 我正在使用devkitpro/devkitarm
  • 我使用NanoBoyAdvance作为模拟器
  • 我用的是Ubuntu
  • 完整命令:
arm-none-eabi-gcc -MMD -MP -MF /path/to/myfile.d  -g -Wall -O1 -mcpu=arm7tdmi -mtune=arm7tdmi -mthumb -mthumb-interwork -iquote /path/to/include -I/opt/devkitpro/libgba/include -I/path/to/build -c /path/to/source/myfile.c -o myfile.o

推荐答案

对内存映射硬件的写入通常需要通过指向volatile种类型的指针来完成,例如*(volatile unsigned int*)0x04000000 = 0x0403;种.否则,编译器可以自由地假设它们没有副作用,并且可以以意想不到的方式进行优化.这里,这既适用于设置视频模式的store ,也适用于放置像素的store .

(实际上,在写入视频内存时,您可以允许进行一些优化;例如,您并不关心所有写入是否完全按照指定的顺序进行,只要它们最终都发生.在这种情况下,您通常可以使用非volatile存储,以及某种编译器特定的内存屏障,就好像它可能会观察它们一样,例如GCC中的asm("" : : : "memory");).

本例中发生的情况是,编译器优化了第一个存储,它设置了视频模式:https://godbolt.org/z/4dn9dhh6j

main:
        ldr     r3, .L3
        mov     r2, #31
        strh    r2, [r3, #240]  @ movhi
.L2:
        b       .L2
.L3:
        .word   100701696

我不是GCC优化器细节方面的专家,但我的假设是,编译器认为在存储和无限循环之间的任何地方都没有从内存中读取数据.因此,它假定程序中的任何代码都不能读回您写入地址0x04000000的值,因为无限循环之后的任何代码(包括main之外的代码)都不能执行.如果该值永远不能被读取,并且写入本身没有副作用(假设它不是volatile),那么它就是dead store,从一开始就没有必要写入它.

然而,按照这种逻辑,人们可能会认为also已经优化了放置像素的存储,但事实并非如此.所以,也许我还遗漏了一些其他的东西.

C++相关问答推荐

Apple Libm的罪恶功能

使用单个字节内的位字段

为什么在此程序中必须使用Volatile关键字?

C:fopen是如何实现二进制模式和文本模式的?

为什么C语言允许你使用var =(struct NAME){

C中函数类型的前向声明

致命:ThreadSaniizer:在Linux内核6.6+上运行时意外的内存映射

这个C程序在工作中途停止获取输入.我收到分段故障(核心转储).我还是不知道问题出在哪里

正数之和是负数

-Wnonnull-Compare警告不是具有误导性吗?

C中的回文数字

Fscanf打印除退出C代码为1的程序外的所有内容

C程序printf在getchar while循环后不工作

如何在Rust中处理C的longjmp情况?

为什么 int32_t 和 int16_t 在 printf 输出中具有相同的位数?

传递参数:C 和 C++ 中 array 与 *&array 和 &array[0] 的区别

(GNU+Linux) 多个线程同时调用malloc()

10 个字节对于这个 C 程序返回后跳行的能力有什么意义

如何在 C 中的 Postgres 函数的表中 for 循环

如何在C中以0x格式打印十六进制值