为什么即使使用volatile关键字,编译器也会因strncmp()而优化掉共享内存读取?

Lon*_*ner 3 c volatile shared-memory compiler-optimization strncmp

这是一个foo.c将数据写入共享内存的程序.

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int main()
{
    key_t key;
    int shmid;
    char *mem;

    if ((key = ftok("ftok", 0)) == -1) {
        perror("ftok");
        return 1;
    }

    if ((shmid = shmget(key, 100, 0600 | IPC_CREAT)) == -1) {
        perror("shmget");
        return 1;
    }

    printf("key: 0x%x; shmid: %d\n", key, shmid);

    if ((mem = shmat(shmid, NULL, 0)) == (void *) -1) {
        perror("shmat");
        return 1;
    }

    sprintf(mem, "hello");
    sleep(10);
    sprintf(mem, "exit");

    return 1;
}
Run Code Online (Sandbox Code Playgroud)

这是另一个bar.c从同一共享内存中读取数据的程序.

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int main()
{
    key_t key;
    int shmid;
    volatile char *mem;

    if ((key = ftok("ftok", 0)) == -1) {
        perror("ftok");
        return 1;
    }

    if ((shmid = shmget(key, sizeof (int), 0400 | IPC_CREAT)) == -1) {
        perror("shmget");
        return 1;
    }

    printf("key: 0x%x; shmid: %d\n", key, shmid);

    if ((mem = shmat(shmid, NULL, 0)) == (void *) -1) {
        perror("shmat");
        return 1;
    }

    printf("looping ...\n");
    while (strncmp((char *) mem, "exit", 4) != 0)
        ;

    printf("exiting ...\n");

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

我首先在一个终端运行编写程序.

touch ftok && gcc foo.c -o foo && ./foo
Run Code Online (Sandbox Code Playgroud)

当编写器程序仍在运行时,我在另一个终端中运行读取器程序.

gcc -O1 bar.c -o bar && ./bar
Run Code Online (Sandbox Code Playgroud)

读者程序进入无限循环.看起来优化器已经优化了以下代码

    while (strncmp((char *) mem, "exit", 4) != 0)
        ;
Run Code Online (Sandbox Code Playgroud)

    while (1)
        ;
Run Code Online (Sandbox Code Playgroud)

因为它在循环中看不到任何东西,mem它可以在读取一次后修改数据.

但我宣布memvolatile正是这个原因; 防止编译器优化它.

volatile char *mem;
Run Code Online (Sandbox Code Playgroud)

为什么编译器仍然优化读取mem

顺便说一下,我找到了一个有效的解决方案.有效的解决方案是修改

    while (strncmp((char *) mem, "exit", 4) != 0)
        ;
Run Code Online (Sandbox Code Playgroud)

    while (mem[0] != 'e' || mem[1] != 'x' || mem[2] != 'i' || mem[3] != 't')
        ;
Run Code Online (Sandbox Code Playgroud)

为什么编译器会优化掉,strncmp((char *) mem, "exit", 4) != 0mem[0] != 'e' || mem[1] != 'x' || mem[2] != 'i' || mem[3] != 't'即使char *mem声明volatile在两种情况下都没有优化掉?

AnT*_*AnT 6

6.7.3类型限定符

6 [...]如果尝试通过使用具有非volatile限定类型的左值来引用使用volatile限定类型定义的对象,则行为未定义.133)

133)这适用于那些行为就好像用限定类型定义的对象,即使它们实际上从未被定义为程序中的对象(例如内存映射输入/输出地址的对象).

这正是您在代码中观察到的内容.编译器基本上是在"行为未定义"的狂野自由下优化代码.

换句话说,不可能strncmp直接正确地应用于易失性数据.

你可以做的是实现你自己的比较,不放弃volatile限定符(这是你已经完成的),或者使用一些易失性感知方法将易失性数据复制到非易失性存储,它们适用strncmp于后者.