这是一个将数据写入共享内存的程序 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;
}

这是另一个程序 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;
}

我首先在一个终端中运行编写器程序。
touch ftok && gcc foo.c -o foo && ./foo

当编写器程序仍在运行时,我在另一个终端中运行读取器程序。
gcc -O1 bar.c -o bar && ./bar

阅读器程序进入无限循环。看起来优化器优化了下面的代码
    while (strncmp((char *) mem, "exit", 4) != 0)
        ;


    while (1)
        ;

因为它在循环中看不到任何可以在读取一次后修改 mem 数据的内容。

但正是出于这个原因,我将 mem 声明为 volatile;以防止编译器对其进行优化。
volatile char *mem;

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

顺便说一下,我找到了一个有效的解决方案。有效的解决方案是修改
    while (strncmp((char *) mem, "exit", 4) != 0)
        ;


    while (mem[0] != 'e' || mem[1] != 'x' || mem[2] != 'i' || mem[3] != 't')
        ;

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

最佳答案

通过编写 (char *)mem 你告诉 strncmp 函数它实际上不是一个 volatile 缓冲区。事实上,strncmp 和其他 C 库函数并非设计用于处理 volatile 缓冲区。

实际上,您确实需要修改代码以不在 volatile 缓冲区上使用 C 库函数。您的选择包括:

  • 编写您自己的 C 库函数替代方案,用于处理 volatile 缓冲区。
  • 使用适当的内存屏障。

  • 你已经选择了第一个选项;但是想想如果另一个进程在你的四次读取之间修改了内存会发生什么。为了避免此类问题,您需要使用第二个选项,即进程间内存屏障——在这种情况下,缓冲区不再需要是 volatile,您可以返回使用 C 库函数。 (编译器必须假设屏障检查可能会更改缓冲区)。

    关于c - 即使使用了 volatile 关键字,为什么编译器会因 strncmp() 而优化掉共享内存读取?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/41051724/

    10-11 23:55