マクロの大文字小文字2011年04月29日 12時40分35秒

マクロ関数の引数名が大文字と小文字を区別するかを調べてみた。

全て大文字、最初だけ大文字、全て小文字のファイルを処理してみる。CASE マクロは単に展開されたら無くなる。展開後は、1, 2, 3 があれば区別されている事になる。


sun% cat x.c 
#define CASE( AAA, Aaa, aaa ) AAA, Aaa, aaa

CASE( 1, 2, 3 )
sun%


sun% CC -E x.c 
#1 "x.c"
#3 "x.c"
1 , 2 , 3 
hp% aCC -E x.c 
#line 1 "x.c"


1, 2, 3

ibm% xlC_r -E x.c 
#line 3 "x.c"
1, 2, 3

freebsd% gcc -E x.c
# 1 "x.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x.c"


1, 2, 3

Sun、IBM、HP そして FreeBSD 共々 1, 2, 3 の出力を出した。マクロの引数名は大文字小文字を区別するらしい。

おまけに Solaris 上での GCC。


sun% g++ -E x.c 
# 1 "x.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x.c"


1, 2, 3

マクロの隙間2011年02月16日 21時37分28秒

実は C/C++ のマクロには落とし穴が隠されている。非常に小さな隙間なのだが、落ちると大きな怪我をすることになる。

#define VAR1 ( arg1 )
#define VAR2( arg2 )

この二つは似て非なるものだ。この穴は時折見つけづらく、余計な時間を浪費することになる。VAR の後の微妙な隙間が大きな違いをもたらす。

VAR1 は置換。ここでは、VAR1 を ( arg1 ) に書き換える。括弧は、マクロの記述で、展開時にも意図した順になるように推奨されている。

VAR2 はマクロ関数。VAR2 は arg2 を引数として取り、その後に続く、式に展開される。ここでは、VAR2 の後に何もないので空文になる。

実際に試してみる。


% cat macro.c 
#define VAR1 ( arg1 )

VAR1( 2 )

#define VAR2( arg2 )

VAR2( 4 )
% cpp macro.c 
# 1 "macro.c"
# 1 "<built-in>"
# 1 "≷command-line>"
# 1 "macro.c"


( arg1 )( 2 )





この例では、マクロ自体は不正ではなくエラーは出ない。コンパイラを通すとこの例ではもちろんエラーになる。

運悪くこの全く異った置換が成功してしまう事もある。コンパイルに失敗すると、意味の不明なエラーに悩まされる。大概にして、奇妙な行数から出て、一見すると間違っていないように見える。だから発見が難しい。たった一つの空白が犯す罪を。

更に運が悪いとコンパイルまで通ることもある。不思議な挙動に悩まされる事になる。デバッガもマクロの追跡はまともに出来ないし、実はマクロの所為で普段とは違ったステップを行なっていたとしても、甚だ見つけづらい。

境界をあわせたメモリをスタックから確保する2011年02月03日 12時17分15秒

スタックからメモリを割り当てる時に境界を合わせるためのマクロ。reinterpret_cast を使っているから C++ 用になっているが、C のキャストで使える。

#define ALIGNED_CHAR_PTR_BY( name, type, size ) \
    type name ## _[ ( size + sizeof( type ) - 1 ) / sizeof( type ) ];\
    char* name = reinterpret_cast< char* >( name ## _ )

ネットワークなどの IPC を通して構造体をスタックに読み込んだ時に、スタックに一時領域として割り当てたメモリが必ずしも境界に合っているとは限らない。それを強制的に合わせるのに使う。構造体の中で一番大きい型を渡すか、単純にシステム上で一番大きい型を渡しても良い。

long 型に沿って最低 13 バイト割り当てる。


ALIGNED_CHAR_PTR_BY( long_aligned, long, 13 );

幾つかのアーキテクチャで試験はしたが、全てのアーキテクチャで絶対的に使えるのかは、自信がない。

C++ 言語 マクロ講座 可変個数引数編2011年01月20日 17時27分06秒

C++ で __VA_ARGS__ がどの様に動くのか調べてみた。C 言語では C99 で制定されているが、C++ ではどうなっているのかは、把握していない。C と C++ のプリプロセッサは、実装として同じものが使われている場合もあり、制定されていなくても動く場合が多い。

実験には C 言語 マクロ講座 可変個数引数編 と同じ物を使う。


% cat vaargs.cpp 
#define xprintf(...) printf(__VA_ARGS__)

xprintf("Hello %s\n", "World");

xprintf("%s %s\n", "Hello", "World");

幾つかのプラットフォームで試す。


sun % CC -E vaargs.cpp
#1 "vaargs.cpp"
#3 "vaargs.cpp"
printf ( "Hello %s\n" , "World" ) ; 

printf ( "%s %s\n" , "Hello" , "World" ) ; 
freebsd % g++ -E vaargs.cpp 
# 1 "vaargs.cpp"
# 1 ""
# 1 ""
# 1 "vaargs.cpp"


printf("Hello %s\n", "World");

printf("%s %s\n", "Hello", "World");
hp % aCC -E vaargs.cpp 
#line 1 "vaargs.cpp"


printf("Hello %s\n", "World");

printf("%s %s\n", "Hello", "World");

ibm % xlC_r -E vaargs.cpp 
#line 3 "vaargs.cpp"
printf("Hello %s\n", "World");

printf("%s %s\n", "Hello", "World");

FreeBSD, Solaris, IBM, HP で __VA_ARGS__ は展開されている。

C++ bitset は使えない2011年01月19日 12時00分48秒

C++ の bitset が使えないと言われる理由はテンプレートクラスだからの様だ。定数を取るテンプレートの為、宣言時に大きさが固定されてしまう。

丁度ビットマップが使いたくなったので、ポインタ型で使って動的に大きさを決めようと思った。

いざ、使おうとすると大きさが固定になるので、動的には決められない。しかし、利用箇所によって大きさがまちまちで、大きく異なるので簡単には決め打ちできないのだ。

提供する関数自体は便利そうだと思ってはいたが、実際に使おうとすると思ったようにオブジェクトを生成できなかった。

負の整数値を static_cast2010年12月05日 05時26分12秒

負の値を static_cast したらどうなるのか気になった。-1 の出力を調べてみる。

sun% cat int2unsigned.cpp 
#include <iostream>

int main()
{
    int i = -1;
    unsigned int u = static_cast<unsigned int>( i );
    std::cout << u << std::endl;
}
sun% CC int2unsigned.cpp
sun%  ./a.out 
4294967295
freebsd% g++ int2unsigned.cpp
freebsd% ./a.out
4294967295

C++ stream のエラーコード2010年09月12日 12時19分32秒

C++ のストリームのエラーがとても分りづらい。good と fail は正反対の関係では無いのだ。そこで例を取り上げて試してみる。

#include <sstream>
#include <iostream>

void status(const char *when, const std::stringstream &s)
{
    std::cout << "checking state "
              << when
              << ":"
              << " good = " << s.good()
              << " bad  = " << s.bad()
              << " eof  = " << s.eof()
              << " fail = " << s.fail()
              << std::endl;
}

int parse(const char *buf)
{
    int value;
    std::stringstream ss(buf);
    status("Before read", ss);
    ss >> value;
    status("After  read", ss);
    return value;
}

int main()
{
    parse("3");
    parse("");

    return 0;
}

最初は、数字を読み込む。そして、次は意図的に読み込みを失敗させる。

checking state Before read: good = 1 bad  = 0 eof  = 0 fail = 0
checking state After  read: good = 0 bad  = 0 eof  = 1 fail = 0
checking state Before read: good = 1 bad  = 0 eof  = 0 fail = 0
checking state After  read: good = 0 bad  = 0 eof  = 1 fail = 1

読んだ後は、両方とも good() が 0 になっている。既に eof に着いたからだ。最初の方は読み込みに問題が無いので fail() が 0 に、しかし二つ目は読み込みが失敗したので、fail() が 1 になっている。

C++ での covariant return type2010年07月18日 12時01分33秒

C++ では、派生型の戻り値で関数をオーバーライドすることが出来る。多態性の例。
struct A
{
    virtual A* f() { return NULL; }
};

struct B : public A
{
    virtual B* f() { return NULL; }
};

同じテンプレートクラスを二度基底クラスにしてみる2010年06月06日 11時41分19秒

今回もまた、同じクラスを基底クラスとして二回指定してみる。すこし違うところはテンプレートクラスだと言うこと。今回のはコンパイルは通るはずだ。


% cat b.cpp 
template
class A
{
};

class B: public A<1>, public A<2>
{
};

int main()
{
}
sun% CC b.cpp
sun%
frebsd% g++ b.cpp
freebsd%
ibm% xlC_r a.cpp
ibm%
hp% aCC a.cpp
hp%

全てのプラットフォームで成功。

前回

同じクラスを二度基底クラスにしてみる2010年06月05日 11時33分24秒

同じクラスを基底クラスとして二度指定したらどうなるか試してみた。これ自体はエラーが出るのは十中八九だが、これ自体は次の実験のための布石。

% cat a.cpp 
class A
{
};

class B: public A, public A
{
};

int main()
{
}
sun% CC a.cpp 
"a.cpp", line 6: Error: The base class A is included more than once.
1 Error(s) detected.
freebsd% g++ a.cpp 
a.cpp:6: error: duplicate base type 'A' invalid
ibm% xlC_r a.cpp
"a.cpp", line 55.17: 1540-0409 (S) "A" must not be used more than once in the lis
t of base classes.
hp% aCC a.cpp
"a.cpp", line 5:error #2263: duplicate base class name
  class B: public A, public A
                            ^


1 error detected in the compilation of "a.cpp".

全てのプラットフォームで失敗。

次回