簡體   English   中英

為什么即使使用volatile關鍵字,編譯器也會因strncmp()而優化掉共享內存讀取?

[英]Why does the compiler optimize away shared memory reads due to strncmp() even if volatile keyword is used?

這是一個程序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的數據。

但出於這個原因,我正確認為memvolatile ; 防止編譯器優化它。

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' mem[0] != 'e' || mem[1] != 'x' || mem[2] != 'i' || mem[3] != 't' mem[0] != 'e' || mem[1] != 'x' || mem[2] != 'i' || mem[3] != 't'即使char *mem在兩種情況下都被聲明為volatile

6.7.3類型限定符

6 [...]如果嘗試通過使用具有非volatile限定類型的左值來引用使用volatile限定類型定義的對象,則行為未定義。 133)

133)這適用於那些行為就好像用限定類型定義的對象,即使它們實際上從未被定義為程序中的對象(例如內存映射輸入/輸出地址的對象)。

這正是您在代碼中觀察到的內容。 編譯器基本上是在“行為未定義”的狂野自由下優化代碼。

換句話說,不可能將strncmp直接正確應用於易失性數據。

您可以做的是實現自己的比較,不丟棄volatile限定符(這是您已經完成的),或使用一些易失strncmp知方法將volatile數據復制到非易失性存儲,並將strncmp應用於后者。

通過寫(char *)mem你告訴strncmp函數它實際上不是一個易失性緩沖區。 事實上, strncmp和其他C庫函數並不適用於易失性緩沖區。

實際上,您需要修改代碼,以便在易失性緩沖區上不使用C庫函數。 您的選擇包括:

  • 編寫自己的替代C庫函數,使用volatile緩沖區。
  • 使用適當的內存屏障。

你已經選擇了第一個選項; 但想想如果其他進程在你的四次讀取之間修改了內存會發生什么。 要避免這類問題,您需要使用第二個選項,即進程間內存屏障 - 在這種情況下,緩沖區不再需要是volatile ,您可以返回使用C庫函數。 (編譯器必須假設屏障檢查可能會更改緩沖區)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM