CS+のコード生成ツールが生成するポインタ変数でvolatile修飾子の記述位置がおかしいものがあるように思います

藤田様の別スレッドに便乗なのですが、CS+のRL78のコード生成ツールが生成するuart通信機能のポインタ変数でvolatile修飾子の記述位置がおかしいものがあるように思います。具体的な変数名で言うと、gp_uart0_tx_addressとgp_uart0_rx_addressです。(CS+ for CC V5.00.00のコード生成機能で確認しました。)

コード生成ツールが生成した変数宣言は以下の通りです。

volatile uint8_t * gp_uart0_tx_address;         /* uart0 send buffer address */

volatile uint8_t * gp_uart0_rx_address;         /* uart0 receive buffer address */

ところが、以下の画面コピーの赤枠の箇所の通り、割り込みルーチン内で変更されるのはgp_uart0_tx_addressやgp_uart0_rx_addressの変数自身ですので以下の変数宣言が正しい気がします。

uint8_t * volatile gp_uart0_tx_address;         /* uart0 send buffer address */

uint8_t * volatile gp_uart0_rx_address;         /* uart0 receive buffer address */

試しに型修飾子の作用の仕方がvolatileと同様なconst(型修飾子の作用自体(機能)としては対義語的になる)で試してみると、変数自身に作用したのは以下の画面コピーのように橙枠の方でしたので、volatileも変数自身に作用させる場合には同様である筈です。

Parents
  • >NULL は何のオブジェクトも指しませんが、逆に、何かのオブジェクトを指すポインタは NULL でないことは確定的です。前段の hogehoge の指す先をアクセスした以降では hogehoge の値は NULL ではないということで、無限ループとなります。

      NULL だったら関数を抜けると言う判断をどこかでしているのなら分かります。
     しかし、そんな事はしていないよね。
      NULL かどうか判断して、「NULLでした」と表示するプログラムは作れないと言うこと?
     NULLをどう使うかはプログラマーしだいだと思います。printfだと0x00の前まで送ると言う仕様だから、それに従うしか無い。
     しかし自分で作った関数で、文字列の先頭アドレスと転送バイト数を指定すれば0x00の所で止める必要も無いわけです。
     言いたいことは、「NULLだったらエラーにしなくてはならず、NULLを使ってはいけません」と言う規則がある訳でも無いだろうと言う事です。

     こういう所では、よくバトルになったりするけれど、攻撃している訳じゃないですよ。
     コンパイルの動作がおかしいんじゃないかと言う話です。
Reply
  • >NULL は何のオブジェクトも指しませんが、逆に、何かのオブジェクトを指すポインタは NULL でないことは確定的です。前段の hogehoge の指す先をアクセスした以降では hogehoge の値は NULL ではないということで、無限ループとなります。

      NULL だったら関数を抜けると言う判断をどこかでしているのなら分かります。
     しかし、そんな事はしていないよね。
      NULL かどうか判断して、「NULLでした」と表示するプログラムは作れないと言うこと?
     NULLをどう使うかはプログラマーしだいだと思います。printfだと0x00の前まで送ると言う仕様だから、それに従うしか無い。
     しかし自分で作った関数で、文字列の先頭アドレスと転送バイト数を指定すれば0x00の所で止める必要も無いわけです。
     言いたいことは、「NULLだったらエラーにしなくてはならず、NULLを使ってはいけません」と言う規則がある訳でも無いだろうと言う事です。

     こういう所では、よくバトルになったりするけれど、攻撃している訳じゃないですよ。
     コンパイルの動作がおかしいんじゃないかと言う話です。
Children
  • リカルドさん、こんにちは。藤田様、フォロー有難う御座います。

    リカルドさまwrote: said:
    >1 void hoge(void)
    2 {
    3 extern volatile int* hogehoge;
    4 while (*hogehoge) ;
    7 while (hogehoge) ;
    10 }

    hoge:
    movq .refptr.hogehoge(%rip), %rax
    movq (%rax), %rdx
    .L2: while (*hogehoge)に相当
    movl (%rdx), %eax
    testl %eax, %eax
    jne .L2
    .L3:              while (hogehoge)に相当
    jmp .L3       hogehoge がゼロでない事を何処で判断したの?
    .text
    リカルドさまwrote: said:
    >NULL は何のオブジェクトも指しませんが、逆に、何かのオブジェクトを指すポインタは NULL でないことは確定的です。前段の hogehoge の指す先をアクセスした以降では hogehoge の値は NULL ではないということで、無限ループとなります。

     NULL だったら関数を抜けると言う判断をどこかでしているのなら分かります。
     しかし、そんな事はしていないよね。
     NULL かどうか判断して、「NULLでした」と表示するプログラムは作れないと言うこと?
     ...略...
     コンパイルの動作がおかしいんじゃないかと言う話です。

    それは、このx86 Windows(あるいはLinux)用のGCCの最適化機能に関して私がリンクを書いたKMC(京都マイクロコンピュータ社)のスタッフさんが書かれたブログの以下の話に結びつくものではないかと思います。

    私は、これは GCC の問題というよりは、ポインタを NULL チェック無しでいきなり参照している Linux Kernel のコードの問題だと思います。しかし、Kernel などのセキュリティが重要なソフトウェアをビルドする際には、fno-delete-null-pointer-checks を付けておいた方が良さそうです。

    # あるいは、NULL チェックが行われていないポインタ参照全てにデフォルトのチェックコードを挿入するようなオプションがあれば良いのかもしれません。

    また、この最適化に関しては以下のリンク先のように、リカルドさんと同じく、いろいろ思うところのある人もいると思います。

    Qiita投稿(この人の投稿の真ん中あたり(余談の項より上で))
    qiita.com/AoiMoe/items/2554f78dc9c197d22109

  • >  NULL だったら関数を抜けると言う判断をどこかでしているのなら分かります。

    > しかし、そんな事はしていないよね。

    明示的に NULL チェックを行い、ポインタの値が NULL である可能性があることをコンパイラに伝えれば NULL だった場合は安全に関数を抜けるコードを吐きますよ。

    $ cat -n hoge.c
         1  void hoge(void)
         2  {
         3      extern volatile int* hogehoge;
         4      if (hogehoge) {
         5          while (*hogehoge) {
         6              ;
         7          }
         8      }
         9      while (hogehoge) {
        10          ;
        11      }
        12  }
    
    $ gcc -Wall -W -S -O2 hoge.c -o -
            .text
    hoge:
            movq    .refptr.hogehoge(%rip), %rax
            movq    (%rax), %rdx
            testq   %rdx, %rdx
            je      .L1
    .L5:
            movl    (%rdx), %eax
            testl   %eax, %eax
            jne     .L5
    .L4:
            jmp     .L4
    .L1:
            rep ret
    
    $
    
  • > NULLをどう使うかはプログラマーしだいだと思います。printfだと0x00の前まで送ると言う仕様だから、それに従うしか無い。

    > しかし自分で作った関数で、文字列の先頭アドレスと転送バイト数を指定すれば0x00の所で止める必要も無いわけです。

    NULL と '\0' を混同されているのでは?

    > 言いたいことは、「NULLだったらエラーにしなくてはならず、NULLを使ってはいけません」と言う規則がある訳でも無いだろうと言う事です。

    NULL が何のオブジェクトも指さないことは『プログラミング言語 C』の初版から書かれていることで、世に数多ある C コンパイラも ISO/IEC 9899:1999 等の標準規格を前提として作成されているので、それ以外の使い方をされたいのであれば自己責任ということになります。

    例えば NULL ポインタアクセスの検出をしたいとして

    void hoge(void)
    {
        *(uint32_t*)NULL = 0xDEADBEEF;
    }
    

    のようなコードを書いたとして、プログラマが期待した動作をするかは不明です。Cygwin64 上の gcc で試したところ

    $ cat -n hoge.c
         1  #include <stdint.h>
         2  #include <stddef.h>
         3
         4  void hoge(void)
         5  {
         6      *(uint32_t*)NULL = 0xdeadbeef;
         7  }
    
    $ gcc -v
    Using built-in specs.
    COLLECT_GCC=gcc
    COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-pc-cygwin/5.4.0/lto-wrapper.exe
    Target: x86_64-pc-cygwin
    Configured with: /cygdrive/i/szsz/tmpp/gcc/gcc-5.4.0-1.x86_64/src/gcc-5.4.0/configure --srcdir=/cygdrive/i/szsz/tmpp/gcc/gcc-5.4.0-1.x86_64/src/gcc-5.4.0 --prefix=/usr --exec-prefix=/usr --localstatedir=/var --sysconfdir=/etc --docdir=/usr/share/doc/gcc --htmldir=/usr/share/doc/gcc/html -C --build=x86_64-pc-cygwin --host=x86_64-pc-cygwin --target=x86_64-pc-cygwin --without-libiconv-prefix --without-libintl-prefix --libexecdir=/usr/lib --enable-shared --enable-shared-libgcc --enable-static --enable-version-specific-runtime-libs --enable-bootstrap --enable-__cxa_atexit --with-dwarf2 --with-tune=generic --enable-languages=ada,c,c++,fortran,lto,objc,obj-c++ --enable-graphite --enable-threads=posix --enable-libatomic --enable-libcilkrts --enable-libgomp --enable-libitm --enable-libquadmath --enable-libquadmath-support --enable-libssp --enable-libada --enable-libgcj-sublibs --disable-java-awt --disable-symvers --with-ecj-jar=/usr/share/java/ecj.jar --with-gnu-ld --with-gnu-as --with-cloog-include=/usr/include/cloog-isl --without-libiconv-prefix --without-libintl-prefix --with-system-zlib --enable-linker-build-id --with-default-libstdcxx-abi=gcc4-compatible
    Thread model: posix
    gcc version 5.4.0 (GCC)
    
    $ gcc -Wall -W -S -O2 hoge.c -o -
            .text
    hoge:
            movl    $0, 0
            ud2
    
    $
    

    不正命令例外をおこす命令を出力しました。

  • >例えば NULL ポインタアクセスの検出をしたいとして
    >void hoge(void) {*(uint32_t*)NULL = 0xDEADBEEF;}
    >のようなコードを書いたとして、プログラマが期待した動作をするかは不明です。
    >Cygwin64 上の gcc で試したところ・・・不正命令例外をおこす命令を出力しました。

     これってNULLポインタの所に書き込みだからまずいんじゃないかな。読み出しならば問題無いと思います。
     
     皆さんの書き込みを見て、私の次の書き込みの答えがモヤっと分かったような気がします。

    2017/3/31 1:55
    .L3:              while (hogehoge)に相当
    jmp .L3       hogehoge がゼロでない事を何処で判断したの?
    .text

     NoMaY さん紹介の qiita.com/AoiMoe/items/2554f78dc9c197d22109 も参考にしました。
     「NULL が何のオブジェクトも指さない」と言われても、ゼロ番地からメモリーダンプするのに困るじゃないか。
     NULLポインタを使ってアクセスしたらエラーだよなんて、コンパイラが勝手に判断したら困るだろうと思いました。
     私が使っているのは、組み込みマイコンです。
     fujita nozomu さんのコンパイラはパソコンのプログラム開発のCコンパイラでしょう。
     そのためユーザはNULLポインタの所をアクセスしないと言う前提の元にコンパイルし、前述のコンパイル結果になったのかなと思っています。
  • > これってNULLポインタの所に書き込みだからまずいんじゃないかな。読み出しならば問題無いと思います。

    試してましたが

    $ cat -n hoge.c
         1  #include <stddef.h>
         2
         3  char readNullPtr(void)
         4  {
         5      return *(const char*)NULL;
         6  }
    
    $ gcc -Wall -W -S -O2 hoge.c -o -
            .text
    readNullPtr:
            movzbl  0, %eax
            ud2
    
    $
    

    読み出しでも不正命令例外をおこす命令を出力しますね。

    > fujita nozomu さんのコンパイラはパソコンのプログラム開発のCコンパイラでしょう。

    > そのためユーザはNULLポインタの所をアクセスしないと言う前提の元にコンパイルし、前述のコンパイル結果になったのかなと思っています。

    パソコンのプログラム開発用と限ったものではなく、出力するアーキテクチャは x86 と x64 に対応しており、例えば MMU を持たない80376をターゲットとして Windows や Linux の様な OS を使わない組み込み用途にも使えるものです。

    > 「NULL が何のオブジェクトも指さない」と言われても、ゼロ番地からメモリーダンプするのに困るじゃないか。
    > NULLポインタを使ってアクセスしたらエラーだよなんて、コンパイラが勝手に判断したら困るだろうと思いました。

    C の言語仕様として NULL は何のオブジェクトも指さないことは決められているので、安全確実にそれを行える方法はありません。

  • > NoMaY さん紹介の qiita.com/AoiMoe/items/2554f78dc9c197d22109 も参考にしました。

    記事にある `-fno-delete-null-pointer-checks' のコンパイル指示を gcc に与えると

    $ cat -n hoge.c
         1  #include <stddef.h>
         2  #include <stdint.h>
         3
         4  void writeNullPtr(void)
         5  {
         6      *(uint32_t*)NULL = 0xDEADBEEF;
         7  }
         8
         9  char readNullPtr(void)
        10  {
        11      return *(const char*)NULL;
        12  }
        13
        14  void hoge(void)
        15  {
        16      extern volatile int* hogehoge;
        17      while (*hogehoge) {
        18          ;
        19      }
        20      while (hogehoge) {
        21          ;
        22      }
        23  }
    
    $ gcc -Wall -W -S -O2 hoge.c -o -
            .text
    writeNullPtr:
            movl    $0, 0
            ud2
    
            .text
    readNullPtr:
            movzbl  0, %eax
            ud2
    
            .text
    hoge:
            movq    .refptr.hogehoge(%rip), %rax
            movq    (%rax), %rdx
    .L4:
            movl    (%rdx), %eax
            testl   %eax, %eax
            jne     .L4
    .L5:
            jmp     .L5
    
    $ gcc -Wall -W -S -O2 -fno-delete-null-pointer-checks hoge.c -o -
            .text
    writeNullPtr:
            movl    $-559038737, 0
            ret
    
            .text
    readNullPtr:
            movzbl  0, %eax
            ret
    
            .text
    hoge:
            movq    .refptr.hogehoge(%rip), %rax
            movq    (%rax), %rdx
    .L4:
            movl    (%rdx), %eax
            testl   %eax, %eax
            jne     .L4
            testq   %rdx, %rdx
            je      .L3
    .L8:
            jmp     .L8
    .L3:
            rep ret
    
    $
    

    NULL ポインタ先への読み書きはそれを行うコードが出力され、一度アクセスしたポインタの NULL チェックを省く最適化も行われなくなるようです。

    この辺りの話は使用するコンパイラに依存するものであり、C の言語仕様に反する NULL ポインタアクセスを安全に行う方法がどの処理系にも等しく提供されている、というものではありません。

    > 「NULL が何のオブジェクトも指さない」と言われても、ゼロ番地からメモリーダンプするのに困るじゃないか。

    ゼロ番地からメモリーダンプできて当たり前、と考えるのは間違いです。

  • fujita nozomu さん、実験有難う御座います。
     
     NULL は 「char, for, do」等の予約語と違い、STDDEF.H の中に次のように書かれてますね。
     
    #define NULL  ((void *)0)
     
     標準関数の中に「エラーの時は NULL を返す」とか言うのが有って、そこで「#define NULL ((void *)0)」を使う訳ですね。
     だから組み込みマイコンで標準関数を使わなければ STDDEF.H をインストールする必要も無い。
     「#define NULL  ((void *)0)」が無ければコンパイラは「NULLって何?」となって、ゼロ番地をアクセス禁止にする理由が無いと思います。
     
      5  return *(const char*)NULL;

     じゃ無くて

      5  return *(const char*)((void *)0);

     ならどうでしょう。
  • >  5  return *(const char*)((void *)0);
    >
    > ならどうでしょう

    $ cat -n hoge.c
         1  char readNullPtr(void)
         2  {
         3      return *(const char*)((void *)0);
         4  }
    
    $ gcc -O2 -S hoge.c -o -
            .text
    readNullPtr:
            movzbl  0, %eax
            ud2
    
    $
    

    変わらんですね。