[←1つ前] [→1つ後] [↑質問一覧] [↑記事一覧] [ホームページ]
len = strlen(s); buf = malloc(len); strcpy(buf, s);
'\0'
が必要です。
strlen
が戻す値は、'\0'
を含まないので、
malloc(len)
ではなく
malloc(len + 1)
としなければなりません。
malloc
で得ようとした。
次のコードどちらがよいのか。
/* 1 */ len = strlen(s); buf = malloc(len + 1); /* 2 */ len = strlen(s) + 1; buf = malloc(len);
len
という名前の変
数には文字列の長さを入れておくという一貫した発想でプログラムを作った方が、
修正時のトラブルを防ぐなどの意味があります。
int *array; array = (int *) malloc(sizeof(int) * 16);
malloc
の型は(void *)
となっています。
この型は、他の型のポインタに代入する時に、キャストしなくても自動的に適切
な変換がほどこされることが保証されています。ですから、キャストは必要あり
ません。
“voidへのポインタは、任意の不完全型若しくはオブジェクト型へのポインタ
に、又はポインタから、型変換してもよい。任意の不完全型若しくはオブジェク
ト型へのポインタは、voidへのポインタに型変換してよく、再び戻してもよい。
その結果は元のポインタと比較して等しくなければならない。”
'a'
の型は何か?
'a'
のような定数は文字定数と呼ばれています。
C言語では、文字定数の型はint
であることになっています。
char
だと誤解しそうですが、sizeof('a')
を
調べればchar
ではないことが分かります。
setftime
を使って変更することが
できます。
ところで、実際にディレクトリの時刻を変更できるツールが出回っています。
これらのツールはシステムコールで時刻を変更するのではなく、ディスクの内容
を直接読み書きするシステムコールを使って、直接セクタの内容を処理している
ようです。
なお、絶対にやってはいけませんが、システムの時刻を変更して、ディレクト
リを作成し、その後システムの時刻を元に戻す、という方法があります。
くどいですが、絶対にしないように。
int main(argc, argv) int argc; char *argv[]; { ....
int main(int argc, char *argv[]) { ....
char const *s;
と、char *const s;
は何が違うのか。
const
が修飾する内容が違います。
const
を理解するには、それに続く内容がconst
(変更不可)であるという考え方が分かりやすいと思います。
この考え方に従えば、char const *s;
と書いた場合、
const
であるのは*s
です。すなわち、s
が指している内容は変更できません。
char *const s;
とした場合は、const
であるのは
s
です。すなわち、s
そのものが変更できません。
表にすると次のようになります。
s *s --------------- ------------ ------------ char const *s; 変更できる 変更できない char *const s; 変更できない 変更できる
s
と*s
の両方とも変更させたくない場合は、
char const *const s;
と書きます。
(c.l.c. FAQ 11.9)
memcpy
関数とmemmove
関数は何が違うのか。
どちらを使えばよいか。
memcpy
の実装として、次のようなものを考えます。これは正しい実装です。
void *memcpy(void *s1, const void *s2, size_t n) { char *p1 = s1; const char *p2 = s2; while (n-- > 0) { *p1++ = *p2++; } return s1; }これに対して、次のような呼び出しを考えます。
char buf[] = "This is a sample data"; memcpy(buf + 5, buf, 16);この結果としては、
buf
の中が、次のようになることを期待して
いるのです。
"This This is a sample"しかし、実際はこうなりません。次のようになります。
"This This This This T"先頭からコピーすると、既に上書きしてしまった所からさらにコピーしようと するために、このような結果になるのです。期待通りにコピーするためには、後 ろからコピーしなければなりません。しかし、後ろからコピーするように
memcpy
を実装すると、今度は
memcpy(buf, buf + 5, 16);がうまくありません。
memcpy
については、このような実装が許されています。
厳密にいえば、領域に重複がある場合にはmemcpy
の結果は未定義
です。しかし、これでは困るという場合もあるので、memmove
とい
う関数が用意されています。memmove
は、このようにコピーする領
域に重複がある場合にも期待通りの結果となることを要求されます。
すなわち、memmove
は、一旦どこか別のバッファに目的の文字列を
コピーしてから、その結果は必要な所にコピーしたのと同じ結果になるようにふ
るまいます。
ということは、memmove
だけあれば特に問題ないということにな
るのですが、実装方法によっては、memcpy
の仕様は高速に処理する
場合に有利なこともあるので、重複がないことが確実である場合には
memcpy
を使った方がよいかもしれません。
(参考: c.l.c FAQ list 11.25)
fgetpos
関数とftell
関数は何が違うのか。
どちらを使えばよいか。
fgetpos
はfpos_t
という型の引数を使ってファイル
内の位置を表現しています。ftell
の一つの弱点は、その値が
long
であるということです。long
が32ビットの処
理系の場合は、表現できる範囲は2147483647(0x7fffffff)が最大です。これは約
2Gバイトに相当します。殆どの場合はこれで問題ないと言っても、最近は2Gバイ
トより容量の大きいハードディスクも多くなってきました。一つのファイルのサ
イズが2Gバイト以上だと、ftell
の戻り値で表現することができま
せん。fgetpos
の場合、fpos_t
という型が適切に設定
されていて、かつコードがそれを意識した設計になっている限り、このような大
きなファイルにも対応したプログラムを書くことが可能です。
(参考: c.l.c FAQ list 12.25)
return
しているのに、他では値を
戻さずにreturn
しているような場合を検出することもできます。
lintは複数のソースファイルを指定することもできます。この場合は、定義さ
れているのに使われていない関数を検出することができます。
最近のコンパイラは、細かい警告を出す機能が強化されているため、従来のコ
ンパイラではlintを使わなければ発見できなかったような手違いは、コンパイル
時に発見することができるようになりました。
lintの出す警告は、しばしば「警告しすぎる」という状態になりがちです。こ
れらを抑止するために特別なコメントが使われることがあります。例えば、コー
ドの中に/*NOTREACHED*/
というコメントを書いておけば、lintはそ
の先のコードは実行されないという前提で検査を行います。これは、例えば
exit
関数を呼び出した後に一度も通過しない個所がある場合などに
使われます。
/* lintで警告が出る例。関数の戻り値が一部でのみ使われている場合 */ foo() { if (f1() != 0) { ... } } bar() { f1(); }
/* lintで警告が出る例。関数が戻り値を戻したり戻さなかったりする場合 */ int f1() int a; { if (a & 1) return 1 return; }
remove
を実行して消してしまったファイルを復活するには
どうすればよいか。
for (;;) { extern int status; if (status & 1) break; }この例の場合は、外部変数
status
の状態を監視していますが、
この他には、特
定のアドレスの様子(特にI/Oポート)や、キーボードやマウスの状態、データの受
信バッファの状態、などが検査の対象となります。キーボードが押されたことを
検出したい場合、それをどのようにして知るかは処理系に依存しますが、基本的
に次のようなループを使えば可能です。
for (;;) { if (キーボードが押された) break; }このようなループが嫌われる理由は、主に二つあります。
DOS上で単独のプログラムだけを実行するような場合には、ビジーウェイトは簡 単に書けるために使われることがあります。
Warning test.c 22: Call to function 'printf' with no prototype in function mainのような警告メッセージが出た。何が悪かったのか。
printf
がプロトタイプなしで呼
び出されている」となります。
プロトタイプ宣言というのは、あらかじめ関数の引数および戻り値の型を宣言
しておくというもので、実際に関数が呼び出される所では、宣言された型と実際
に引数として使われている変数の型が一致しない場合には、必要な型への型変換
が自動的に行われます。
コンパイラは、与えられたコードをコンパイルする時に、コード中から呼ばれ
ている関数に対するプロトタイプ宣言を探します。この宣言が見つからない場合
は、とりあえず引数の型がそのままで正しいとしてコンパイルすることになりま
す。警告は、このような状況になったことを意味しています。関数呼び出しの時
に使われている変数や定数の型が、その関数の仕様通りならば特に問題はありま
せん。しかし、引数の型が異なっていると、高い確率で、実行時に予期せぬふる
まいを引き起こしたり、最悪の場合はプログラムが暴走、異常終了するかもしれ
ません。
printf
は標準関数なので、プロトタイプ宣言は対応する
#include
を行うことによって自動的に行われます。この場合は
#include <stdio.h>
をプログラムの先頭に追加すれば解決します。
プロトタイプ宣言を行っていれば、その関数を呼び出す時に型が関数仕様と合
致しない場合には、自動的に型変換が行われます。この場合はキャストする必要
はありません。しかし、場合によっては、プログラマーが意図的に自動的な型変
換を期待したコードを書いたのではなく、間違った変数を引数に与えてしまった
のかもしれません。このような場合には、コンパイル時に警告が出ることもあり
ます。
int printf(const char *format, ...);いちいちリファレンスマニュアルを見てこのような宣言をするのは面倒だが、 よい方法はないのか。
printf
が<stdio.h>
で定義
されている関数ですから、#include <stdio.h>
をコードの先頭に書
けば警告は出なくなります。
long
やsize_t
へのキャストは必要か。
#include <stdio.h> .. foo(FILE *fp) { int a[100]; ... fseek(fp, (long) 256, SEEK_SET); fread(buf, sizeof(int), (size_t) 100, fp); ... }
<stdio.h>
を
#include
することにより、fseek
とfread
のプロトタイプ宣言が行われています。従って、次のように書いても全く同じ結
果となるはずです。
#include <stdio.h> .. foo(FILE *fp) { int a[100]; ... fseek(fp, 256, SEEK_SET); fread(buf, sizeof(int), 100, fp); ... }ところで、キャストを省いてしまうと、例えば
fseek
の2番目の引
数はint
の定数としての256
ということになります。
最終的には、これは<stdio.h>
内にある関数プロトタイプによって、
自動的にlong int
に型変換されてからfseek
が呼び出
されるため、問題はありません。ただ、この場合、コードを読んだだけでは、プ
ログラマーが深く考えずにint
として256
と書いたのか、
あるいはプロトタイプ宣言があるから自動的に型変換が発生することを期待した
のか、判断することができません。もっとも、このような場合に何も考えずに
256
と書けるというのが、プロトタイプ宣言の一つの利点なのかも
しれません。
しかし、fseek
の例のような場合は、キャストしないにしても、
long
の引数であるべき所にint
の定数が書いてある
のは、個人的にはどうも落ち着かない感じがするので、せめて次のように
long
の定数としたい所です。
fseek(fp, 256L, SEEK_SET);このような状況下で、プログラマーがあえて「ここは
long
を書く
べき所だが、いまint
の変数に入っている値をlong
と
して与えてやりたい」という意志を表現したいのであれば、確かにプログラミン
グ上は無駄かもしれませんが、自己主張の一貫としてあえて意図的にキャストを
付けても構わないのではないか、と思います。
なお、これに関しては「いや、無駄だから書かない方がよい」という人もいま
す。文法上はどちらでも構いません。
/* 1 */ if (a == 1) foo(a); else bar(a); /* 2 */ if (a == 1) foo(a); else bar(a);
※ c.l.c FAQ : comp.lang.c FAQ list http://www.eskimo.com/~scs/C-faq.top.html 文中の項目番号は新しい版に対応しています。旧版とは異なります。