4

わかりました、ここからすべてが始まりました: Unsigned integer と unsigned char は同じ値を保持していますが、動作が異なるのはなぜですか?

舞台裏で何が起こっているか (つまり、コンパイラがこの問題をどのように処理しているか) を理解するために、次のアプリケーションを作成しました。

#include <stdio.h>

int main()
{
  {
  unsigned char k=-1;
  if(k==-1)
  {
    puts("uc ok\n");
  }
  }

  {
  unsigned int k=-1;
  if(k==-1)
  {
    puts("ui ok");
  }
  }
}

そして、次のようにGCCでコンパイルします:

gcc -O0 -S -masm=intel h.c 

次のアセンブリ ファイルを取得します。

    .file   "h.c"
    .intel_syntax noprefix
    .section        .rodata
.LC0:
    .string "ui ok"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    push    rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    mov     rbp, rsp
    .cfi_def_cfa_register 6
    sub     rsp, 16
    mov     BYTE PTR [rbp-1], -1
    mov     DWORD PTR [rbp-8], -1
    cmp     DWORD PTR [rbp-8], -1
    jne     .L3
    mov     edi, OFFSET FLAT:.LC0
    call    puts
.L3:
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
    .section        .note.GNU-stack,"",@progbits

そして、驚いたことに、最初のチェックはありません。

しかし、Microsoft Visual C++ (2010) で同じものをコンパイルすると、次のようになります (このリストから多くのゴミを削除したため、あまり有効ではありません)。

00B81780  push        ebp  
00B81781  mov         ebp,esp  
00B81783  sub         esp,0D8h  
00B81789  push        ebx  
00B8178A  push        esi  
00B8178B  push        edi  
00B8178C  lea         edi,[ebp-0D8h]  
00B81792  mov         ecx,36h  
00B81797  mov         eax,0CCCCCCCCh  
00B8179C  rep stos    dword ptr es:[edi]  
00B8179E  mov         byte ptr [k],0FFh  
00B817A2  movzx       eax,byte ptr [k]  
00B817A6  cmp         eax,0FFFFFFFFh  
00B817A9  jne         wmain+42h (0B817C2h)  
00B817AB  mov         esi,esp  
00B817AD  push        offset string "uc ok\n" (0B857A8h)  
00B817B2  call        dword ptr [__imp__puts (0B882ACh)]  
00B817B8  add         esp,4  
00B817BB  cmp         esi,esp  
00B817BD  call        @ILT+435(__RTC_CheckEsp) (0B811B8h)  
00B817C2  mov         dword ptr [k],0FFFFFFFFh  
00B817C9  cmp         dword ptr [k],0FFFFFFFFh  
00B817CD  jne         wmain+66h (0B817E6h)  
00B817CF  mov         esi,esp  
00B817D1  push        offset string "ui ok" (0B857A0h)  
00B817D6  call        dword ptr [__imp__puts (0B882ACh)]  
00B817DC  add         esp,4  
00B817DF  cmp         esi,esp  
00B817E1  call        @ILT+435(__RTC_CheckEsp) (0B811B8h)  

問題は、なぜこれが起こっているのかということです。GCC が最初の IF を「スキップ」するのはなぜですか? GCC がそれをスキップしないようにするにはどうすればよいですか? 最適化は無効になっていますが、まだ何かを最適化しているようです...

4

4 に答える 4

2

確かに非常に小さな問題ですが、GCC の問題のように見えます。

GCCのドキュメントWebサイトから(強調鉱山):

最適化オプションがない場合、コンパイラの目標は、コンパイルのコストを削減し、デバッグで期待される結果を生成することです。ステートメントは独立しています。ステートメント間のブレークポイントでプログラムを停止すると、任意の変数に新しい値を割り当てたり、プログラム カウンターを関数内の他のステートメントに変更したりして、ソース コードから期待どおりの結果を得ることができます。

したがって、 との-O0間にブレークポイントを配置し、そのブレークポイントの変更中に、分岐が行われることを期待できるはずです。しかし、それは発行されたコードでは不可能です。unsigned char k=-1;if(k==-1)k

于 2013-05-27T12:21:55.440 に答える
0

ここにはいくつかの細かい点があります。

  • コンパイラは、条件 k==-1 が unsigned char の場合に決して true にならないことを証明するために、k の初期化を調べる必要さえありません。ポイントは、符号なしの8 ビット値を 32 ビットに昇格する必要があるということです。これは、比較の右側が整数定数であり、デフォルトで 32 ビットであるためです。k は署名されていないため、このプロモーションの結果は になります00000000 00000000 00000000 xxxxxxxx。定数 -1 にはビット パターン11111111 11111111 11111111 11111111があるため、何が何であれxxxxxxxx、比較の結果は常に false になります。
  • この点は間違っているかもしれませんが、k が volatile として指定されていたとしても、コンパイラはそれをレジスタにロードするだけで済みます (ロード操作がハードウェアで何らかの望ましい副作用を引き起こす可能性があるため)。実際に比較を実行するか、到達不能な if ブロックのコードを生成します。
  • 実際、到達不能コードのアセンブリの生成を省略することは、コンパイル プロセスを高速化するという -O0 の目的に完全に従っています。
  • 私の知る限り、符号なし定数と負の定数の比較は未定義の動作です。少なくとも、ケースを正しく処理するための機械命令は存在せず、逆アセンブリからわかるように、コンパイラはソフトウェアで処理するために必要なコードを挿入しません。得られるのは、符号付きと符号なしの間の暗黙的なキャストであり、整数オーバーフロー (それ自体が未定義の動作) と、混合されていない符号の比較になります。
于 2013-06-02T14:25:08.547 に答える