質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.12%

volatileが必要な場面を見つけ出す

解決済

回答 6

投稿

  • 評価
  • クリップ 6
  • VIEW 7,783

strike1217

score 583

例えば、以下のコード

#include<signal.h>

sig_atomic_t interrupted;

void sigint_handler(int signum){
     interrupted = 1;
}

int main(){
     signal(SIGINT, sigint_handler);
     while(!interrupted){
            [何かの処理]
     }
     return 0;
}


このコードは sig_atmoic_t interrupted にvolatileが付いていないので最適化するとバグります。

main()からの読み取りは、コンパイラの最適化によって削除されてしまう可能性がある。

真っ先に思ったこと:「知らねぇぇぇ・・・ & なんで??」
最適化によって削除されたり、変更されたりする変数を把握することができません。
つまり、volatileが必要、不必要な場面がわからない。ということです。

変数宣言にvolatile修飾子を追加すると、intterruptedはwhileループの繰り返しやシグナルハンドラからアクセスされる際に、必ず元のアドレスからアクセスされることが保証される。
キャッシュできないデータにはvolatileを使う

(キャッシュ?これは・・・CPUに備わっているキャッシュのことですかね?
元のアドレスとはなんのこと?)

皆さんは、どうやってvolatileが必要な部分を見つけているんでしょうか?

プログラムを作る -> 最適化するとバグる -> volatileが必要な部分を探す。

しかし、これだとバグを前提にvolatileを探すことになるので、良い方法とは言えませんが・・・

あるパターンが存在するはずです。
volatile修飾の規則を教えてください。
しかし、全パターンを覚えるんですか??
それは流石に無理ですよね?

_[ここから私の妄想]___________
volatileによる最適化抑止機能は、変数に対してに加えて、関数ごとに抑止できると便利ではないでしょうか?

volatile int buffer_ready;
char buffer[BUF_SIZE];

void buffer_init(){
     for(size_t i = 0; i < BUF_SIZE; i++)
          buffer[i] = 0;
     buffer_ready = 1;
}


簡単な関数の例なのですが、

volatile宣言がされている場合、コンパイラはそのデータが置かれたメモリに対する読み書きの順序を変更できない。しかし、これらの読み書きと、他のメモリに対する読み書きとの順序を変更してもよい。
したがって、コンパイラはこのループをbuffer_readyへの書き込みの後に移動することができる。これはプログラマの意図に反する。

今回の場合は、buffer_readyが入れ替わっても問題無さそうですが、複雑な関数になると問題になってくるはずです。
そうすると、bufferの方にもvolatileを付ける羽目になり、volatileがどんどん増えていきます。

buffer_init()そのものを最適化抑止させる機能があると便利ではないでしょうか?

ラムダ式って何が便利なのです??
以前の質問で、main()を内をさらに細かい関数単位で分割できる・・・と言ったんですが、すると関数の中もラムダ式を使って細かく最適化抑止を制御できるのではないか?(ラムダ関数を最適化抑止する)
という想像ができます。
ラムダ式の利便性が向上しそうです。
__________________________

まぁ、私の妄想はどうでもいいんですけど・・・関数全体に対して最適化抑止機能が付いているのはどうかなぁ??と思っただけの話なのです。

ここでの疑問は「どうやって、volatile修飾が必要な箇所を見つければ良いんでしょうか??」という疑問です。
パターンがあるなら、是非教えてください。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 6

checkベストアンサー

+8

(この回答はC言語またはC++言語を前提とします)

どうやって、volatile修飾が必要な箇所を見つければ良いんでしょうか??

普通のPC/サーバ上で動作する一般的なアプリケーションを開発しているときは、volatile修飾のことを気にする必要はありません。

真にvolatile修飾が必要となるのは、以下のパターンいずれかと思います:

  • シグナルハンドラ(signal handler)を記述するとき。
  • デバイスドライバなど外部ハードウェアとのI/O処理を直接記述するとき。
  • 組込機器/マイコン/DSP上で動作するプログラムを記述するとき。
  • オペレーティング・システム(OS)本体を記述するとき。

補足:「マルチスレッドアプリ開発ではvolatileが必要」という主張は、C/C++言語においては全くの誤りです。これらは昔のC/C++開発環境を前提としているか、他言語の話題と混同されているケースが大半です。


プログラムを作る -> 最適化するとバグる -> volatileが必要な部分を探す。 
しかし、これだとバグを前提にvolatileを探すことになるので、良い方法とは言えませんが・・・

volatile修飾を「正しく」利用する(付けるべきところで修飾し、余計なところには使わない)のはとても難しい問題です。機械的に正誤判定を行う方法は私も知りません。最終的にはプログラマ自身が正しい知識を身につけることと、レビュープロセスなどで人的にチェックするしかないと思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/02/19 21:23

    yohhoyさん。追記ありがとうございます。少し噛み砕いてみます。

    キャンセル

  • 2018/02/19 22:36

    では、「コンパイラからは検知できない任意タイミングで更新される可能性のある変数」の場合のみ考慮すれば良いのですね。

    ん〜〜私の妄想の部分は問題にはならないんですね。

    そうなると、volatileが必要となる場面は結構少ない・・・んですね。
    殆どの場合は最適化を施しても問題になるケースは少ない・・・と考えて良いんですね。

    キャンセル

  • 2019/11/08 17:30

    > 真にvolatile修飾が必要となるのは、以下のパターンいずれかと思います:

    どれでもないパターンでvolatileが必要となったことがありまして、ガベージコレクタの存在するRuby用のC言語拡張を書いているときに、ガベージコレクタによる回収を防いで「ここまでは変数を生き残らせなければならない」ということを表現するためにvolatileが登場したことがあります。

    キャンセル

+3

こんにちは。

最適化は基本的に関数内で行われますから、関数内での変更だけであれば、おかしな動作にならないように最適化されます。(当然ですね。)
しかし、関数の外(割り込みや他のスレッド)で変更されるとコンパイラはその状況を把握できませんので、プログラマが教えて上げる必要があります。
つまり、関数が動作中に、割り込みや他のスレッドにて変更される変数にvolatileを付けます。


【yohhoyさんとの議論にて追記】
データ競合が発生する場合は、一般にロックやアトミック変数でデータ競合を回避します。
対スレッドの場合はその機構により、最適かも抑止されるのでvolatileは付けなくて良いと標準規格にて決まっているそうです。
ただし、対シグナルハンドラーについては標準規格でvolatileを付ける必要があるそうです。

シグナルハンドラーを実装するケースは稀でしょうから、ちゃんとデータ競合を回避しておけば、一般にはvolatileは付けなくても大丈夫と考えて良さそうです。
質問のサンプルはシグナルハンドラーですので、volatileが必要なようです。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/02/19 15:46

    ああ!なるほど!
    シグナルは割り込みと見なせるので、シグナルによって変更される変数にvolatileを付ける。
    と考えるわけですね。

    つまり、主にはグローバル変数でしょうかね?
    ローカル変数にはvolatileは付けないということですよね?(割り込みやスレッドによって変更されないから)

    キャンセル

  • 2018/02/19 16:16

    確かに事実上それでOKと思います。
    ↓で議論したのと同様、スレッドや割り込みハンドラーで共有する変数については同様に注意が必要です。
    https://teratail.com/questions/113653

    排他制御の問題と最適化抑止の問題は一緒に出てくるので混同しやすいですね。

    キャンセル

  • 2018/02/19 16:20

    「排他制御の問題と最適化抑止の問題は一緒に出てくるので混同しやすいですね。」
    ああ!そうですね。

    とりあえず、関数外の要因に関わる変数にはvolatileを!・・・は理解しました。

    キャンセル

+2

訂正:

すみませんが、自分の回答はvolatileを付けないとどうなるかの一例を挙げたにすぎずvolatileをどう使うかというトピックにはあまりに不十分であると思います。yohhoyさん回答からそう気づきました。

例えばコンパイラーが生成するコードのみならずプロセッサーでもメモリーオーダリングが行われており、それを制御するのにはメモリーバリアを意識しなくてはならず、かつC/C++のvolatileはプロセッサによるメモリーオーダリングを制御するようなメモリーバリア機能を持たないということがyohhoyさん回答からわかりました。

そうした点だけとっても「volatileはそんな簡単に直接使えるようなものではない」と思いました。


自分の認識では「複数のスレッド(より厳密に言えば非同期に実行されるもの=割り込みなども含む)から並行して更新・参照される制御情報」にvolatileを付けるというのが判断基準です。

gccのコンパイルの一例ですが

int flag = 1;
main() {
  while (flag) {
  }
}


これを-Ofastでコンパイルすると

main:
.LFB23:
  .cfi_startproc
  movl  flag(%rip), %eax
  testl %eax, %eax      ; (A)
  je  .L2
.L4:
  jmp .L4
  .p2align 4,,10
  .p2align 3
.L2:
  xorl  %eax, %eax
  ret

となりました。このコードを見るとvolatileがないとなぜ困るかは自明と思います。プログラム開始直後に一度だけ行われる(A)の判断結果により「無限ループするか」「一度もループしないか」が決められてしまい、ループしながらflagの変化を待つという動作になりません。

複数のスレッドが更新・参照する可能性がある情報を扱う場合、特定のスレッドで過去のある時点にアクセスした結果得られた値を後で再び用いること、例えばレジスターなどへコピーした値を再度用いることはもちろんNGですよね?

キャッシュとは「プロセッサーから遠い場所にある情報のコピーをより近い場所に置いてそれを参照する」ということを表します。プロセッサーのL1/L2キャッシュとは限りません。最初に示したコンパイル結果が表しているように、レジスターやスタックも本件においてはキャッシュの一種として捉えるべと思います。

そのような「オリジナルとは別の場所にキャッシュされた情報を参照することを許さない」のがvolatileの効果と考えるのが第一歩で、そうされては困る情報にvolatileを付けるのが(少なくとも基本的な)判断基準だと思います。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/02/19 16:01 編集

    むむ・・・
    「レジスターやスタックも本件においてはキャッシュの一種として捉えるべと思います。」
    これについては納得いきました。

    「オリジナルとは別の場所にキャッシュされた情報を参照することを許さない」
    「オリジナル」とは、例えば、KSwordOfHasteさんのコードに載っているflagグローバル変数のようなものの事ですよね?

    キャンセル

  • 2018/02/19 17:26

    > flagグローバル変数のようなもの
    自分の回答意図はそのとおりです。

    ただ、回答してはみたもののyohhoyさんコメントを拝見するに自分の回答はかなり不十分な回答だと思います。volatileをどのように使うかは簡単なトピックではなく、むしろ「それを使わない方法を学ぶ」方が一般アプリケーションプログラマーには優先すべきトピックじゃないかと思います。

    キャンセル

+1

(キャッシュ?これは・・・CPUに備わっているキャッシュのことですかね?
元のアドレスとはなんのこと?) 

この文脈におけるキャッシュとは、レジスタに値を保持して使い回すことを言います。元のアドレスとは、変数が格納されているアドレスのことです。
つまり、ある変数が関数の中で何度も繰り返し使用されるとき、最適化しない場合はその都度メモリ領域から変数の値を読み取りますが、最適化が有効な場合は、最初に一度メモリ領域から読み出してその値をレジスタに覚えておき、後はそれを使い回す、ということをします。

皆さんは、どうやってvolatileが必要な部分を見つけているんでしょうか? 
あるパターンが存在するはずです。
volatile修飾の規則を教えてください。

volatileは探して見つけるようなものではありませんし、パターン化された規則によって付けるかどうかを決めるものでもありません。付けるかどうかは「設計上の必要性」に応じて決めます。
その変数がどのタイミングで誰がアクセスするかは、ちゃんと設計しているなら「当然把握している」はずなので、裏(別スレッドとか割り込みとか)で変更される可能性のあるもので、前述のような最適化をしてほしくない変数ににはvolatileを付けることになります。


一応、「_[ここから私の妄想]_______」について追記

buffer_init()そのものを最適化抑止させる機能があると便利ではないでしょうか? 

問題の解決を誤った方法に求めると、より問題が深刻化します(あるいは潜在的な問題に気づけなくなります)。その手の制御は、最適化の有無などという不確かなもので行うのではなく、排他制御で行うのが普通です。buffer_readyとbufferへのアクセスのアトミック性を確保しさえすれば、メモリのアクセス順がどうなろうと問題にはなりません。


追記

私の経験的な見解としては、一般的なアプリを作る上ではvolatileはほとんど必要性を感じません。パフォーマンスのチューニングをしたいときに作る検証用コードで、最適化によって変数のアクセス回数が減って正しく計測できなくなるのは困る、というときに使う程度です。少なくとも、製品向けのプログラムでvolatileを使ったことはありません。

volatileの話題でマルチスレッドの話がよく出てきますが、同期目的で使えるのは、せいぜいフラグ変数程度でしょう。実際には、スレッド間/プロセス間の同期は、プラットフォームが提供する専用のAPIを使うのが最も確実です。製品を作る上では不確実な要素は排除しないといけませんから。
C++11でスレッドが導入されてからは、プラットフォーム依存のAPIを直接呼び出さなくても済むようになりましたね(標準ライブラリーにない便利な同期オブジェクトは直接API呼び出しすることになりますが)。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/02/19 16:37

    あ、つまり、「最初に一度メモリ領域から読み出してその値をレジスタに覚えておき、後はそれを使い回す」
    これをされると困る時に、volatileを付けるわけですね。

    キャンセル

  • 2018/02/19 16:40

    そういうことです。

    キャンセル

  • 2018/02/19 16:40

    なるほど!理解いたしましたわ。

    キャンセル

+1

パターンというか私ならここでvolatileを使うというケースです。

  • ポーリング待ち用の変数にはつける。
  • I/Oにアクセスするためのポインタ変数にはつける。

最近のCPUはOutOfOrder実行が普通なので最適化の有無に関係なく変数にアクセスする順番が守られない可能性があります。
その場合はメモリフェンス命令によって次に実行する命令を待たせたりするのですが、マルチコアCPUをターゲットにした環境の場合はミューテックスやロックの関数にその機構が実装されてるのかもしれませんね。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

ちょっとまとめます。他の人の回答に出てくるものだと・・・

・コンパイラからは検知できない任意タイミングで更新される可能性のある変数
・レジスタなどにコピーしたものを使いまわされては困る
・「volatile宣言がされている場合、コンパイラはそのデータが置かれたメモリに対する読み書きの順序を変更できない。しかし、これらの読み書きと、他のメモリに対する読み書きとの順序を変更してもよい」という規則

スレッドセーフな関数について
以前の質問では「安全」を定義する必要がありましたが、今回では「問題」を定義する必要がありますね。

・プログラム自体のバグが問題である場合、確かにvolatileが必要となる場面は意外に少ない・・・かと思います。
・プログラマが意図しない動作が問題である場合、volatileが必要になる場面は結構ある・・・と思います。

例えば、以前の質問・・・
64bit整数型が遅い理由
で、上げているプログラムなのですが・・・
これに最適化を施すと割り算がループの外に出てしまい(ループが不要なので消える)、この実験プログラムには意味がなくなってしまいます。
正常に動作はしますが、そういう意味においては、「問題」になるため、volatileが必要になってきます。

いやぁぁ・・・む・ず・か・し・い・!ですね。

これはまさにcatsforepawさんの仰る通り、

付けるかどうかは「設計上の必要性」に応じて決めます。

しかし、実際にどうやってvolatileを見つけるかなのですが・・・
最適化によって変更される規則は、おそらくコンパイラのオプティマイザーの開発者レベルじゃないと完璧に把握するのは無理なんでしょう。

yohhoyさんの仰る通りかもしれません。

最終的にはプログラマ自身が正しい知識を身につけることと、レビュープロセスなどで人的にチェックするしかないと思います。 

よく出てくるパターンのみを把握しておけばある程度は「ここでvolatileが必要だな・・・」という感覚が身につくかも・・・しれないです。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/02/20 00:03 編集

    よく出てくると言ってもここでは、最適化による変更のパターンを4つしか上げていませんね。
    むぅぅぅ・・
    手強いですね。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.12%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる