2016-12-09 9 views
2

ここには、共有メモリにデータを書き込むプログラムfoo.cがあります。volatileキーワードが使用されていても、strncmp()によって共有メモリの読み込みを最適化するのはなぜですか?

#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; 
} 

私はライタープログラムを最初に1つの端末で実行します。

touch ftok && gcc foo.c -o foo && ./foo 

ライタープログラムが実行されている間、私は別のターミナルでリーダープログラムを実行します。

gcc -O1 bar.c -o bar && ./bar 

リーダプログラムは無限ループに入ります。それはそれは一度読まれた後memでデータを修正することができるループでは何も見ないので、オプティマイザは

while (1) 
     ; 

に次のコード

while (strncmp((char *) mem, "exit", 4) != 0) 
     ; 

を最適化しているように見えます。

しかし、memvolatileと正確に宣言しました。コンパイラがそれを最適化しないようにします。

volatile char *mem; 

なぜコンパイラはまだmemの読み込みを最適化しますか?

ところで、私は解決策を見つけました。機能するソリューションは

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

while (strncmp((char *) mem, "exit", 4) != 0) 
     ; 

を修正するために、なぜそれがコンパイラがstrncmp((char *) mem, "exit", 4) != 0を離れて最適化していることですが、char *memは、両方のケースでvolatileであると宣言されていても離れてmem[0] != 'e' || mem[1] != 'x' || mem[2] != 'i' || mem[3] != 't'最適化しませんか?

答えて

2

(char *)memを書くことによって、strncmp関数に実際には揮発性バッファではないことを伝えています。実際には、strncmpと他のCライブラリ関数は、揮発性バッファで動作するようには設計されていません。

実際には、揮発性バッファでCライブラリ関数を使用しないようにコードを変更する必要があります。あなたのオプションには次のものがあります。

  • 揮発性バッファーで動作するCライブラリ関数の独自の選択肢を書いてください。
  • 適切なメモリバリアを使用してください。

あなたは最初のオプションを使用しました。他のプロセスがあなたの4つの読み込みの間にメモリを変更した場合に起こることを考えてください。この種の問題を回避するには、2番目のオプション、つまりプロセス間メモリバリアを使用する必要があります。その場合、バッファはもはやvolatileである必要はなく、Cライブラリ関数を使用して戻ることができます。 (コンパイラはバリアチェックがバッファを変更するかもしれないと仮定しなければならない)。

5

6.7.3タイプ修飾子

[...]の試みは、非と左辺の使用を介して揮発性修飾型で定義されたオブジェクトを参照するためになされた場合volatile修飾された型の場合、動作は未定義です。 133)

133)これは、それらが実際にそのようなメモリマップにおけるオブジェクトのようなプログラムのオブジェクト(のように定義されることはないとしても、それらは修飾型で定義されたかのように挙動するそれらのオブジェクトに適用されます入出力アドレス)。

これはまさにあなたのコードで観察していることです。コンパイラは、基本的に、 "動作は未定義です"という野生の自由の下でコードを最適化しています。

つまり、正しく揮発性データにstrncmpを直接適用することはできません。

volatile修飾子(既に行っていることです)を破棄しない独自の比較を実装するか、揮発性データを不揮発性ストレージにコピーする揮発性対応の方法を使用して適用しますstrncmpと後者。

関連する問題