[lang:ja] CとC++による挙動の違い

mbed Advent Calendar 2016の1日目の記事です。

つい先日開催されていたET 2016という展示会のIARブースに、こんなクイズがありました。mbed OS 5で、IARのコンパイラにも対応することが要件となって以来、EWARMユーザーの坪井です。

インチキプログラマであるところの僕は、演算子の優先順位を完全に覚えているわけもなく、Cのコードはコンパイラに食わせて処理するのが正しいという信念に基づき、手元のCコンパイラが入っているマシンとmbedのオンラインコンパイラに処理をさせてみました。

Q1

サイズの定まらないintを使うのではなく、uint8_tなどを使いましょうというのはさておき、出題されていたコードは、次の通りでした。

#include "stdio.h"
int main(void)
{
    unsigned int a = 1;
    unsigned int b = 1;
    unsigned int c = 2;
    unsigned int d = 2;
    
    unsigned int result = a << b * c + d;
    
    printf ("result: %d", result);
    
    return 0;
}


これを、ささっと手元のMacで書いてコンパイルと実行をすると、こんな感じです。

yoshi$ gcc test.c
test.c:9:38: warning: operator '<<' has lower precedence than '+'; '+' will be evaluated first
      [-Wshift-op-parentheses]
    unsigned int result = a << b * c + d;
                            ~~ ~~~~~~^~~
test.c:9:38: note: place parentheses around the '+' expression to silence this warning
    unsigned int result = a << b * c + d;
                                     ^
                               (        )
1 warning generated.
yoshi$ ./a.out
result:16

clangは親切ですね。ちゃんと優先順位を教えてくれました。

手元のマシンにCコンパイラが入っていないという人向けのソリューションが、mbedのオンラインコンパイラです。

#include "mbed.h"
Serial pc(USBTX, USBRX);
int main(void)
{
    unsigned int a = 1;
    unsigned int b = 1;
    unsigned int c = 2;
    unsigned int d = 2;
    
    unsigned int result = a << b * c + d;
    
    pc.printf ("result: %d", result);
    
    return 0;
}


オンラインコンパイラにこんなコードを書いて、ビルドします。できあがったバイナリを手元にある適当なmbedで実行すると、こんな感じです。超簡単ですね。僕は、目の前に転がっていたTY51822r3を使いました。(mbed 2.0を使っています。)

Q2

超簡単に答えを得ることができたところで、Q2に取りかかりましょう。

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int s1,s2;
    char *p1, *p2;

    s1 = sizeof('z') * 10;
    s2 = sizeof(char) * 10;

    p1 = malloc(s1);
    p2 = malloc(s2);

    printf("s1+s2= %d\n", s1+s2);

    return 0;
}


こいつも、ささっと手元のMacでコンパイルしてみます。

yoshi$ gcc test2.c
yoshi$ ./a.out
s1+s2=50

いけました。

では、オンラインコンパイラでやってみます。

#include <mbed.h> // stdlib.hはmbed.hでincludeされています。
Serial pc(USBTX, USBRX);

int main()
{
    int s1,s2;
    char *p1, *p2;

    s1 = sizeof('z') * 10;
    s2 = sizeof(char) * 10;

    p1 = malloc(s1);
    p2 = malloc(s2);

    pc.printf("s1+s2= %d\n", s1+s2);

    return 0;
}


すると、こんなエラーとワーニングが出て怒られました。

Error: A value of type "void *" cannot be assigned to an entity of type "char *" in "main.cpp", Line: 12, Col: 9
Error: A value of type "void *" cannot be assigned to an entity of type "char *" in "main.cpp", Line: 13, Col: 9
Warning: Variable "p1" was set but never used in "main.cpp", Line: 7, Col: 12
Warning: Variable "p2" was set but never used in "main.cpp", Line: 7, Col: 17

怒られたので、7行目をコメントアウトし、12行目と13行目を次の様に書き換えてキャストしてみます。 (ところで、p1とp2は出力に使っていないのに、なぜ計算しているのでしょうね…)

    char *p1 = (char *) malloc(s1);
    char *p2 = (char *) malloc(s2);


無事、コンパイルが通るようになったので、実行してみました。

ありゃ、結果が違ってきちゃいましたね。

Macのclang(LLVM)でCで書いてコンパイルしたものを実行すると、a1が40でa2が10です。対して、オンラインコンパイラコンパイルしたものを実行すると、a1が10でa2が10でした。ということで、「sizeof('z')」の計算結果が違うということが分かりました。

mbedのプログラムは拡張子cppですので、C++です。Macでコンパイルしたときには、test2.cとしたため、Cのコードとしてコンパイルがされています。Cでは'z'がintとして扱われるので4バイト、C++ではcharとして扱われるので1バイト、ですので結果に違いが出たわけです。

yoshi$ gcc test2.cpp
yoshi$ ./a.out 
s1+s2=20

試しに、拡張子をcppにしてコンパイルし、実行をすると、結果は20でした。


Please log in to post comments.