2014/03/27

「C/C++プログラミングの「迷信」と「誤解」」を読んだ

図書館で見かけたC/C++プログラミングの「迷信」と「誤解」を読んで知ったこと。
自分みたいなC++に中途半端な人にはとても有用だった。Cの細かいところとか落とし穴的なところを押さえるにもいいと思う。
C/C++迷信集 | 株式会社きじねこで読めるのかもしれない。
C++についてのみ。Cについては知識/経験なさすぎなのでスルー。
実行環境としてはVisual C++ 2013でWin32 コンソール アプリケーションで空のプロジェクトを作ってTest.cppなファイルにコードを書いてビルド&実行。

一重引用符の中には一文字しか書けない!?(p54)

コード:
// 一重引用符の中には一文字しか書けない!?(p54)
#include <iostream>
#include <ios>

int main(int argc, char* argv[]) {
    const auto ns = 'a';  // 1文字の場合はchar型
    const auto nm = 'abcd';  // 複数文字の場合はint型で値は処理系依存
    std::cout << std::hex <<
        typeid(ns).name() << ", " << ns << std::endl <<
        typeid(nm).name() << ", " << nm << std::endl <<
        std::endl;

    const auto ws = L'a';
#pragma warning(push)
#pragma warning(disable: 4066)
    const auto wm = L'abcd';  // warning C4066: ワイド文字定数の文字列は無視されます。
#pragma warning(pop)
    std::wcout << std::hex <<
        typeid(ws).name() << ", " << ws << std::endl <<
        typeid(wm).name() << ", " << wm << std::endl <<
        std::endl;

    return 0;
}
実行結果:
char, a
int, 61626364

wchar_t, a
wchar_t, a
多文字リテラル(multicharacter literal)の存在とかそれがint型になるとか全く知らなかった。
これに関してはワイド文字は素直でいい。

absは負の値を返さない!?(p123)

コード:
// absは負の値を返さない!?(p123)
#include <cmath>
#include <limits>
#include <iostream>

int main(int argc, char* argv[]) {
    const int x = std::numeric_limits<int>::min();  // -2147483648 トラップ表現
    const int y = std::abs(x);
    std::cout <<
        x << std::endl <<
        y << std::endl <<
        std::endl;

    return 0;
}
実行結果:
-2147483648
-2147483648
説明されてみれば確かにそうなんだけど全く意識したことがなかった。これ踏んでるコードってけっこうありそうな気がするんだけどこれって初見なのでどうなんだろうと不安がある。
absなんか使った記憶ほとんどないけど使いまくりなfabsはどうだろうと確認してしまった。

ポインタを型変換してもアドレスは変わらない!?(p158)

コード:
// ポインタを型変換してもアドレスは変わらない!?(p158)
#include <iostream>

class Foo {
public:
    Foo() {}
    virtual ~Foo() {}
};

class Bar {
public:
    Bar() {}
    virtual ~Bar() {}
};

class Baz: public Foo, public Bar {
public:
    Baz(): Foo(), Bar() {}
    virtual ~Baz() {}
};

int main(int argc, char* argv[]) {
    Baz baz;
    Foo *pfoo = &baz;
    Bar *pbar = &baz;
    Baz *pbaz = &baz;
    std::cout <<
        "pfoo: " << pfoo << std::endl <<
        "pbar: " << pbar << std::endl <<
        "pbaz: " << pbaz << std::endl <<
        std::endl;
    return 0;
}
実行結果:
pfoo: 001BF79C
pbar: 001BF7A0
pbaz: 001BF79C
結構初歩的なことな気がするけど知らなかった。正直恥ずかしい気とまずいコードを書いたことがあるかもしれない。
pfooとpbazの値が一致しないケースを作れない点で自分の低スキルぶりを再認識。

配列や構造体をmemsetで一括ゼロクリア!?(p174)

コード:
// 配列や構造体をmemsetで一括ゼロクリア!?(p174)
#include <iostream>

struct Foo {
    char *c;
    int n;
    double d;
    wchar_t *wcs[10];
};

void write(const Foo& foo) {
    const char* x = reinterpret_cast<const char*>(&foo);
    for (size_t n = 0; n < sizeof Foo; ++n) {
        std::cout << int(*x++);
    }
    std::cout << std::endl;
}

int main(int argc, char* argv[]) {
    //Foo foo;
    //Foo foo = {0};
    Foo foo = {};
    write(foo);

    //Foo *pfoo = new Foo;
    Foo *pfoo = new Foo();
    write(*pfoo);
    delete pfoo;

    //Foo *fs = new Foo[10];
    Foo *fs = new Foo[10]();
    write(fs[0]);
    write(fs[1]);
    write(fs[9]);
    delete[] fs;

    return 0;
}
実行結果:
00000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000
memsetとかstd::fillとかZeroMemoryを使ったりしてサーセン、理解or覚えてなかった。