にほんブログ村 IT技術ブログへ
にほんブログ村
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
C
C 言語でめったに使わないキーワードを集めて見ました。

・signed
符号ありの変数を定義する。int などの整数は符号あり/なしを指定しない場合、符号ありとなる。もちろん明示的に指定しても間違いではない。

・register
変数の領域を(メモリ上ではなく)レジスタに確保するようにコンパイラに指示する。ループのインデックスなどのように非常に頻繁に使う変数にこの register を指定すると、常にレジスタに置かれるので効率が良くなる。
しかし、どの変数をレジスタに置くかという制御はコンパイラ(オプティマイザー)がやってくれるので、プログラマが自ら指定する必要はない。

・auto
自動変数を定義する。スコープから抜けるとその変数はメモリから自動的に削除される。関数内で変数を定義するときのスコープのデフォルトは auto(自動変数)なので、これを指定することはまずない。

・entry
僕自身もう10年以上 C 言語をやってきたけど、一度も使ったことがありません。C99 ではもう削除されていると思われる。

・restrict
これも一度も使ったことなし。オブジェクトへ常にポインタを経由して参照することを明示的に指定する。C99 では削除済み?

役に立たないネタに付き合っていただき、ありがとうございました。



スポンサーサイト
C
if 文の条件式に x == 0 と書くべきところを間違って x = 0 と書いてしまうというミスは、おそらく誰にでも経験があるのではないでしょうか。もちろん、僕もやってしまった経験があります。

対策としては、以下のように引数の順序を逆にするという方法がありました。

if ( 0 == x ) {
...
}



この記法はヨーダ記法というらしいです。この書き方なら定数に値を代入することができないため、コンパイルエラーとなります。しかし、僕はこの記法を初めて見たときには、とても奇妙な印象を受けました。おそらく、順序を逆にするとソースコードが不自然で読みにくくなるからです。それに、この記法が有効なのは左辺の引数が定数の場合だけなので、変数の場合はこの記法は役に立ちません。

条件式の書き方が自然で分かりやすいのは、以下のような書き方です。

左辺                                右辺
調査対象の式(変化する)  比較対象の式(あまり変化しない)



この書き方なら引数の順序が自然で、条件式について考えやすくなり、ソースコードも読みやすくなります。「0 が x の値かどうか」より、「x の値が 0 かどうか」のほうが自然で分かりやすいです。

僕がお薦めする対策方法は、警告を有効にする方法です。

#include <stdio.h>

int main(int argc, char **argv) {
   int x = 0;

   if (x = 0) {
   ...
   }

   return 0;
}


-Wall オプションを指定してコンパイルすると、以下のような警告メッセージが出力されます(GCC 4.1.2 の場合)。

$ gcc -Wall sample.c
sample.c: In function ‘main’:
sample.c:6: 警告: 真偽値として使われる代入のまわりでは、丸括弧の使用をお勧めします


おそらくプログラマが何かミスをしているんじゃないか、ということをコンパイラが親切に教えてくれます。なので、ヨーダ記法の出番はもはやないでしょう。

if 文の条件式の件に限らず、常にコンパイラの警告を有効に設定しておくべきです。


C
ある二つの構造体の値を比較するときには、どうすれば良いでしょうか。少なくとも、以下のような方法は使ってはいけません。

struct {
  int a;
  short b;
  int c;
} data1, data2;

data1.a = 1;
data1.b = 2;
data1.c = 3;

data2.a = 1;
data2.b = 2;
data2.c = 3;

// 構造体を比較する間違った方法
// この方法はやってはいけない!
if (memcmp(&data1, &data2, sizeof(data1)) == 0) {
  printf("data1 と data2 は等しい\n");
} else {
  printf("data1 と data2 は等しくない\n");
}



CPU が Intel 系の場合、この構造体のメンバー b と c の間には、パディングの領域ができます。その領域には、(ゼロで初期化していなければ)おそらくゴミのデータが入っているでしょう。memcmp() は渡された領域にあるビットデータを単純に比較するだけです。そのゴミデータも一緒に比較してしまうので、間違った答えが返されます。

構造体を比較するための標準的な方法は存在しません。なので、構造体の各メンバーを地道に比較する関数を書かなければなりません。

ここで気を付けなければいけない点があります。構造体にメンバーを追加したときに、比較する関数に追加したメンバーを比較する処理を追加するのを忘れてしまう可能性があるということです。対策としては、構造体を定義している場所のすぐ近くにコメントで注意書きを書いておくことが考えられますが、それでも防ぎきれるわけではありません。

この問題について、上手くアドバイスをするのは難しいですね。


今回の話は、特に C プログラマーは必見です。

C では文字列の終端には、常にヌル文字を付加する必要があります。バッファに文字列を格納するときには、memset(3) でバッファをゼロで初期化する処理を書いているかと思います。

char buffer[256];

memset(buffer, 0, sizeof(buffer));
strncpy(buffer, "I am string.", 12);


このようにバッファを使う前に常にゼロで初期化しておけば、ヌル文字が既に設定されているのでヌル文字を付加し忘れるのを防ぐ、という効果があるように思われます。このような考え方の人は多いのではないでしょうか。僕も、最近まではそのような考え方でした。

しかし、最近は考え方が違ってきています。

上記のようにあらかじめゼロクリアしておくのは、一見すると良さそうに思えます。しかし、実際にはこの考え方は良くない、ということに最近になって気付きました。

(繰り返しになってしまいますが)バッファに文字列を書き込んだら、常に終端にヌル文字を付加する必要があります。あらかじめバッファをゼロで初期化してあると、「既にヌル文字が入っているから、ヌル文字を書き込む処理は省略しても問題はないだろう」といった悪い考えでヌル文字を付加する処理を怠ってしまうという、悪い癖が付くことを助長してしまいますつまり、バッファがあらかじめゼロクリアされているという前提条件に依存してしまうのです。バッファは常に初期化されているとは限らないので、そのようなことを勝手に前提条件としてはいけないのです。

また、性能上の問題もあります。以下のようなことを考えてみてください。

1.ゼロクリアする

char buffer[256];

memset(buffer, 0, sizeof(buffer));
strncpy(buffer, "I am string.", 12);


2.ゼロクリアしない

char buffer[256];

strncpy(buffer, "I am string.", 12);
buffer[12] = '\0';


上記の1.ではあらかじめゼロクリアしてから文字列を書き込んでいます。2.では、ゼロクリアしていないため、buffer には、きっとゴミが入っています。そのバッファに文字列を書き込み、終端に文字列を付加しています。1.と2.のどちらの方法でも、文字列の終端にはヌル文字が付加されていることになるので、結果は同じです。つまり、結果が同じになるということは、1.のようにあらかじめ memset(3) を呼んでバッファをゼロクリアするというのは、結局時間の無駄になるだけということになります。しかも、ゼロクリアする処理がループの内側にあった場合は、そのループの回数分だけ無駄な処理が動いていまうことになります。

文字列を書き込む処理とヌル文字を書き込む処理は、つねに一緒に(同時に)行わなければならないのです。くれぐれもバッファが初期化してあると思わないほうが良いです。


C の main() のプロトタイプ宣言といえば、同じみですね。

int main(int argc, char *argv[]);


argc はコマンドラインで指定されたコマンド自身も含めた引数の数、argv はそれぞれの引数文字列のアドレスを格納した配列です。
じつは、C の main() には、三つ目の引数があるのをご存知でしょうか。

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

int main(int argc, char *argv[], char *arge[])
{
    while (*arge++) {
        printf("%s\n", *arge);
    }
   
    return EXIT_SUCCESS;
}


引数 arge には何が入るかというと、環境変数の文字列のアドレスが渡されます。このプログラムを実行すると、以下のような感じになります。

・実行結果(抜粋)

CVS_RSH=/bin/ssh
LANG=C.UTF-8
NUMBER_OF_PROCESSORS=2
OS=Windows_NT
PROGRAMFILES=C:\Program Files
PROMPT=$P$G
SESSIONNAME=Console
SHLVL=1
TERM=cygwin
WINDIR=C:\Windows
_=./hack2.exe


つまり、引数 arge を使って環境変数を取得できるようになります。
しかし、 getenv(3) を使っても同じように環境変数を取得できます。

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

int main(int argc, char *argv[])
{
    printf("CVS_RSH=%s\n", getenv("CVS_RSH"));
    printf("LANG=%s\n", getenv("LANG"));
    printf("NUMBER_OF_PROCESSORS=%s\n",
             getenv("NUMBER_OF_PROCESSORS"));
    printf("OS=%s\n", getenv("OS"));
    printf("PROGRAMFILES=%s\n", getenv("PROGRAMFILES"));
    printf("PROMPT=%s\n", getenv("PROMPT"));
    printf("SESSIONNAME=%s\n", getenv("SESSIONNAME"));
    printf("SHLVL=%s\n", getenv("SHLVL"));
    printf("TERM=%s\n", getenv("TERM"));
    printf("WINDIR=%s\n", getenv("WINDIR"));
    printf("_=%s\n", getenv("_"));
   
    return EXIT_SUCCESS;
}


・実行結果

CVS_RSH=/bin/ssh
LANG=C.UTF-8
NUMBER_OF_PROCESSORS=2
OS=Windows_NT
PROGRAMFILES=C:\Program Files
PROMPT=$P$G
SESSIONNAME=Console
SHLVL=1
TERM=cygwin
WINDIR=C:\Windows
_=./hack3.exe


環境変数の値を取得するだけであれば、getenv(3) を使えば欲しい環境変数名を指定して取得できるので使いやすいです。指定した環境変数が存在しない場合は、NULL が返却されます。

で、この引数 arge ですが、どういうときに使うかというと・・・、じつは、使うことはほとんどありません。上記で説明したように、getenv(3) で環境変数を取得できるからです。それに引数 arge のほうは、環境変数文字列のアドレスを含んだ配列が丸ごと全部渡されるので、ループして欲しい環境変数を探す必要があります。しかも、文字列は name=value の形式で渡されるため、値(value)の部分のみ取り出さないといけないので、使いづらさこの上ないです。そんなわけで、引数 arge の使い道はほとんどないわけです。

これは僕の勝手な想像ですが、おそらく getenv(3) が作られる以前では、この引数 arge を使って環境変数を取得していたのではないか、と思います。あくまで、想像です。

今回はトリビア的なお話でした。


/* 引用 */ blockquote { padding: 0.3em 0.3em 0.3em 0.5em; width: 80%; border-left: 5px solid #4a0400; background-color: #AFEEEE; font-size: 0.875em; overflow: auto; } blockquote pre, blockquote pre code { margin: 0; overflow: auto; }
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。