[英]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
的數據。
但出於這個原因,我正確認為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'
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庫函數。 您的選擇包括:
你已經選擇了第一個選項; 但想想如果其他進程在你的四次讀取之間修改了內存會發生什么。 要避免這類問題,您需要使用第二個選項,即進程間內存屏障 - 在這種情況下,緩沖區不再需要是volatile
,您可以返回使用C庫函數。 (編譯器必須假設屏障檢查可能會更改緩沖區)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.