

以下に示すコードが何故実行できるのかわかりません.  
`(2)` において、添字は`-1` になっていると思いますが、何故エラーが発生しないのですか?

[code]

        #include <stdio.h>
        #include <string.h>
    
        int main()
        {
          char s[] = "hello";
    
          int len = strlen(s);
          printf("len: %d\n", len);               // (1)
    
          printf("%c\n", s[strlen(s) - 1 - len]); // (2)
          // printf("%c\n", s[-1]);               -> compile error
          // printf("%c\n", s[strlen(s) - 6]);    -> (2) と同じ
    
          return 0;
        }
    
[/code]

実行結果 `(2)` の `printf` は改行のみの印字です.

[code]

        len: 5
    
[/code]

コメントの通り、`s[-1]` では警告が表示されるだけで、コンパイルエラーにはなりませんでした.


CC BY-SA 3.0
2015-09-03T14:35:10.260
15206
2015-09-04T20:53:09.580
2015-09-04T01:22:37.243
11191
11190
question
c
C : 配列の添字について


strlen()の結果が、コンパイル時ではなく実行時に返ってくるからでは？  
ゴリゴリに最適化すればコンパイル時に計算してしまうかもしれないけど。


CC BY-SA 3.0
2015-09-03T15:07:48.350
15207
2015-09-03T15:07:48.350
5203
15206
answer


まず最初に: 発生しているのは、 h2so5 さんのおっしゃる通り、 warning だと思います。コンパイラのオプション次第で、 warning は
error になります。

次に、(2) でコンパイル時に warning
が発生しない理由ですが、むしろ、`s[-1]`の場合には、明らかに間違っていそうなコードであると、コンパイラがたまたま理解できたので、 warning
を出力している、と考えた方がいいかもしれません。

最後に、実際に実行してなぜエラーにならないか、ですが、`s[-1]`は、「メモリ上に配置されている"hello"の、hの一つ手前の番地(hの一つ前)」を参照しに行きます。そのメモリ上に格納されていた何かしらが、`s[-1]`の結果として取得され、`printf`で文字コードとして出力されます。

結果には、「改行のみの印字」とありますが、端末で表示できない文字コードが出力されている可能性が高いです。(自分の環境では [DEL
コード](http://www.theasciicode.com.ar/ascii-control-characters/delete-ascii-
code-127.html)でした)


CC BY-SA 3.0
2015-09-03T16:50:58.927
15209
2015-09-03T16:59:34.637
2015-09-03T16:59:34.637
754
754
15206
answer


提示例は [c](/questions/tagged/c "'c' のタグが付いた質問を表示") や
[c++](/questions/tagged/c%2b%2b "'c++' のタグが付いた質問を表示")
でいうところの「未定義動作」を引き起こしています。  
言語仕様書による専門用語「未定義動作 (undefined behavior) 」とは  
\- 誤ったプログラム、または誤ったデータに対する動作であって  
\- 言語仕様書はその動作に対して何も規定しない  
です。

起こりうる動作としては  
\- 処理系が独自の正しい動作を規定する (Visual C の `fflush(stdin);` 等)  
\- コンパイラが翻訳時点で診断し、指摘する (提示例 `s[-1]` )  
\- 診断の結果、コンパイルエラーになるかもしれない  
\- 診断の結果、警告になるかもしれない  
\- リンカがリンク時点で診断し、指摘する (結果は同上)  
\- 実行時にライブラリ関数や OS 等が診断し、実行時エラーで強制終了となる  
\- 診断等が一切なされずにプログラムとしては動作を続けるが  
\- あたかも「プログラマが勝手に期待している通りに」動作する  
\- プログラマの期待に背いて変な動作をする (提示例 (2))  
\- いわゆる「暴走」をする  
\- その他の動作

ということで

> 何故エラーが発生しないのですか?

そもそも「エラーとして検出される」ことは期待してはいけないのです。


CC BY-SA 3.0
2015-09-03T23:30:32.180
15214
2015-09-03T23:30:32.180
8589
15206
answer


`strlen(s) - 1 - len`は実行時にしか値は確定しませんので、コンパイラーがエラーもしくは警告できるとは限りません。

なお、Visual C++の場合ですが、コンパイラー本体では警告されませんでしたが、コード分析による静的解析では

> warning C6385: Reading invalid data from 's': the readable size is '6'
> bytes, but '-1' bytes may be read.

と[C6385警告](https://msdn.microsoft.com/ja-
jp/library/ms182088\(v=vs.100\).aspx)が出力されることを確認しました。

* * *

なお、`s`がポインターでなく固定文字列であることを利用して`strlen(s)`の代わりに`sizeof s -
1`を使用すると、実行時でなくコンパイル時に計算が行われるようになりますので、警告できるコンパイラーの種類は増えるはずです。


CC BY-SA 3.0
2015-09-04T01:45:10.390
16216
2015-09-04T20:53:09.580
2015-09-04T20:53:09.580
4236
4236
15206
answer


gcc に限って言えば、以下の様なマクロを追加して `-O2` オプション(optimize level 2)を付けてコンパイルすると warning
を表示してくれます。

[code]

    #define strlen(s) (sizeof(s)/sizeof(s[0]))
    
[/code]

# まぁ、何の意味も無い事ではありますが

[code]

    $ gcc -O2 -Wall -o example example.c
    example.c: In function ‘main’:
    example.c:12:19: warning: array subscript is above array bounds [-Warray-bounds]
       printf("%c\n", s[strlen(s) - 1 - len]); // (2)
    
[/code]

ところで、gcc には実行時に配列の領域外アクセスをチェックするためのオプションスイッチがあります(`-fsanitize=address` オプション)。

[code]

    $ gcc --version
    gcc (Ubuntu 4.9.2-10ubuntu13) 4.9.2
    
    $ gcc -O2 -g -Wall -fsanitize=address -o example example.c
    $ ./example 
    len: 6
    =================================================================
    ==18994==ERROR: AddressSanitizer: stack-buffer-underflow on address 0x7ffd971e6c1f at pc 0x400940 bp 0x7ffd971e6bf0 sp 0x7ffd971e6be0
    READ of size 1 at 0x7ffd971e6c1f thread T0
        #0 0x40093f in main example.c:12
        #1 0x7f5778126a3f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x20a3f)
        #2 0x4009d8 in _start (example+0x4009d8)
    
    Address 0x7ffd971e6c1f is located in stack of thread T0 at offset 31 in frame
        #0 0x40086f in main example.c:7
    
      This frame has 1 object(s):
        [32, 38) 's' <== Memory access at offset 31 underflows this variable
    HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
          (longjmp and C++ exceptions *are* supported)
    SUMMARY: AddressSanitizer: stack-buffer-underflow example.c:12 main
    Shadow bytes around the buggy address:
      0x100032e34d30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
      0x100032e34d40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
      0x100032e34d50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
      0x100032e34d60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
      0x100032e34d70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    =>0x100032e34d80: f1 f1 f1[f1]06 f4 f4 f4 00 00 00 00 00 00 00 00
      0x100032e34d90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
      0x100032e34da0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
      0x100032e34db0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
      0x100032e34dc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
      0x100032e34dd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    Shadow byte legend (one shadow byte represents 8 application bytes):
      Addressable:           00
      Partially addressable: 01 02 03 04 05 06 07 
      Heap left redzone:       fa
      Heap right redzone:      fb
      Freed heap region:       fd
      Stack left redzone:      f1
      Stack mid redzone:       f2
      Stack right redzone:     f3
      Stack partial redzone:   f4
      Stack after return:      f5
      Stack use after scope:   f8
      Global redzone:          f9
      Global init order:       f6
      Poisoned by user:        f7
      Contiguous container OOB:fc
      ASan internal:           fe
    ==18994==ABORTING
    
[/code]


CC BY-SA 3.0
2015-09-04T07:01:06.353
16237
2015-09-04T07:01:06.353
15206
answer