INASOFT 管理人のひとこと


フリーソフトダウンロードサイト「INASOFT」の管理人 矢吹拓也 が日々の「ひとこと」を語るページです。
2021年1月1日より、旧ブログ(blog.inasoft.org)からお引越ししました。
・INASOFT Webサイト: https://www.inasoft.org/
・管理人のふたこと(長文記事/寄稿文): https://www.inasoft.org/talk/
本業の方のお仕事が再び忙しくなりつつあるので、断続的にしばらく更新が止まることがあります。

目次 | ←前へ / 2014-11-17 00:00 / 次へ→ / 最新へ⇒

■C言語のORはどのように実装される?

2014/11/17 0:00:00


RSSRSS配信中

https://www.inasoft.org/








先日、Twitter上で「プログラマを探すには」みたいなジョークネタが流行っていました。

プログラマが考えている常識と、一般の人の考えている常識の違いを利用するというもので、例えば「=」という記号を「等しい」とみなすか「代入」とみなすか、とか、「AまたはB」という言葉を、「AとBのどちらか一方で、両方ともではない」とみなすか「AとBのどちらか一方、あるいは両方とも」とみなすか、の違いとかですね。

さて、そんな「または」なのですが、論理和としての「または」なのか、あるいは条件分岐としての「または」なのかで、ちょっと意味が変わってきたりもします。

C言語で言うと「|」と「||」の違いですね。

c = a | b;

と書いた場合、aとbを構成するすべてのビットの論理和が計算されますし、仮にaとbが副作用の伴う式であったとした場合は、aもbもどちらも評価されます。

(例えば、 c = a() | b(); と書いた場合は、a() の関数呼出しは行われるし、b() の関数呼出しも行われる)

次に

c  = a || b;

と書いた場合、おそらく c に入るのは、0か0以外(おそらく1)という保証しかされません。論理和の計算ではなくて、式を評価するための計算が行われます。また、ショートサーキット(短絡評価)が行われるため、aが真ならばbの評価は行われません(行われてはならない)。

(例えば、 c = a() || b(); と書いた場合は、a() の関数呼出しは行われるが、b() の関数呼出しが行われるとは限らない。a()の結果が真ならばb()の呼出しは行われてはならない)

さて、C言語のコンパイル結果は、究極にまで最適化されるのが常ですから、これがアセンブリコードにどうコンパイルされるのか、見物です。

実際、Visual Studio 2010 で試してみました。

コンパイルしたプログラムは、こんな感じ。


#include "stdio.h"

int main()
{
    unsigned a;
    unsigned b;
    unsigned c;

    scanf("%d,%d", &a, &b);

    c = a || b;
    printf("%d\n", c);

    return 0;
}


c = 1||2のようにしてしまうと、実際には c = 1という最適化がされてしまって、面白くありません。

また、a = 1; b = 2; c = a || b; と書いたとしても、コンパイラの高度な最適化によって c = 1 というコードが生成されたりしてしまいます。最近のコンパイラは大変頭が良い。

素直にOR演算のコンパイル結果を知りたい場合は、関数の引数を利用するとか、上で書いたようなscanfの結果を利用するなどしなければなりません。

さて、そのコンパイル結果のアセンブリコードは、こんな感じになっておりました。




	TITLE	*****
.686P
.XMM
include listing.inc
.model flat

INCLUDELIB OLDNAMES

PUBLIC ??_C@_05KABNCBHK@?$CFd?0?$CFd?$AA@ ; `string'
PUBLIC ??_C@_03PMGGPEJJ@?$CFd?6?$AA@ ; `string'
EXTRN @__security_check_cookie@4:PROC
EXTRN __imp__printf:PROC
EXTRN __imp__scanf:PROC
; COMDAT ??_C@_03PMGGPEJJ@?$CFd?6?$AA@
CONST SEGMENT
??_C@_03PMGGPEJJ@?$CFd?6?$AA@ DB '%d', 0aH, 00H ; `string'
CONST ENDS
; COMDAT ??_C@_05KABNCBHK@?$CFd?0?$CFd?$AA@
CONST SEGMENT
??_C@_05KABNCBHK@?$CFd?0?$CFd?$AA@ DB '%d,%d', 00H ; `string'
CONST ENDS
PUBLIC _main
; Function compile flags: /Ogtp
; COMDAT _main
_TEXT SEGMENT
_b$ = -8 ; size = 4
_a$ = -4 ; size = 4
_main PROC ; COMDAT
; File c:\mvs8\myprojects\consoletest\consoletest.cpp
; Line 7
push ebp
mov ebp, esp
sub esp, 8
; Line 12
lea eax, DWORD PTR _b$[ebp]
push eax
lea ecx, DWORD PTR _a$[ebp]
push ecx
push OFFSET ??_C@_05KABNCBHK@?$CFd?0?$CFd?$AA@
call DWORD PTR __imp__scanf
add esp, 12 ; 0000000cH
; Line 14
cmp DWORD PTR _a$[ebp], 0
jne SHORT $LN3@main
cmp DWORD PTR _b$[ebp], 0
jne SHORT $LN3@main
xor eax, eax
jmp SHORT $LN4@main
$LN3@main:
mov eax, 1
$LN4@main:
; Line 15
push eax
push OFFSET ??_C@_03PMGGPEJJ@?$CFd?6?$AA@
call DWORD PTR __imp__printf
add esp, 8
; Line 17
xor eax, eax
; Line 18
mov esp, ebp
pop ebp
ret 0
_main ENDP
_TEXT ENDS
END


赤字で示した部分が、該当箇所。

片っぽずつ cmp で評価し、条件ジャンプで飛んでますね。

(ちなみに cmp(比較命令)は、内部的には sub(減算)の結果をフラグレジスタにだけもたらし、減算結果の値そのものはレジスタへ格納しないという命令。cmp(sub)の引数に与えられた数値同士が等しければ、引き算の結果は0になるから、ZF(ゼロフラグ)=1になるということを利用する)

で、その後のjne(ZF≠1なら指定のアドレスへジャンプ)。

そりゃそうか、ショートサーキットを実現するには、これが一番手っ取り早い。

(ショートサーキットは実現する義務があるからね)

こういうのを研究するのも面白いんだよなぁ。



目次 | ←前へ / 2014-11-17 00:00 / 次へ→ / 最新へ⇒


目次の表示:


ブログではないので、コメント機能とトラックバック機能は提供していません。ご質問・ご意見等はメールフィードバックまたはTwitter等からお願いします。いただいたご質問・ご意見などは、この「管理人のひとこと」の記事に追加、あるいは新規の記事にする形で一部または全文をそのまま、あるいは加工させていただいた上で、ご紹介させていただく場合があります。
当サイトでは掲載内容による不具合等に関する責任を持ちません。また、内容の正確性についての保証もありませんので、情報をご利用の際は、利用者の自己責任で確認をお願いします。本ページは公開から1年半後の任意のタイミングで削除される予定です。




2560973 (+0120)[+0708]

Copyright© 2010-2021 INASOFT