プログラミング系 Q&A 1


Index
  1. DOS の改行コードの ^M を \n に変換するのにはどうすればよいか ?
  2. C で、行をまたがる文字列定数を書くには?
  3. fprintf()の "%d%d%d%d.." を複数行に渡って書きたいのだが。
  4. C で、文字列定数領域の生存期間は?
  5. MS-DOS で、Ctrl-Z を入力した直後の printf() が出力されない。
  6. malloc() を 2 回呼んだ時、前とは別に再度メモリーを確保するのか?
  7. 長さが不定なファイルを読み込む場合、バッファの扱いとして良い方法はあるか?
  8. C で、同じ #define が複数あるのは文法的に誤りか?
  9. new で領域を獲得したまま exit した場合、その領域は解放されるか?
  10. C++ で、auto_ptr とは何か ?
  11. fork() で子プロセスを生成した後、wait() せずに処理を続けたいのだが。
  12. 可変引数関数に対して、char、float の値を引数として渡せるか?
  13. va_start(va_list ap, last) の第二パラメタには何を渡すのか?
  14. 派生クラスのオブジェクトを基底クラスのポインタに入れてる場合、正しいデストラクタが呼ばれるか?
  15. printf("%d", 42340); とすると、-23196 と表示されると本に書いてあったが?
  16. ポインタが指すものが文字列かどうかを判定する方法はあるか?
  17. #if 0 の働きは?
  18. printf("VAL = %.2lf\n", 0.045); が、0.04 と表示されるのは何故?

1. DOSの改行コードの ^M を \n に変換するのにはどうすればよいか ?

UNIX 上で DOS のファイルをエディターで開くと、行末に ^M があるように見える時があるが、DOS の改行コードは ^M ではなく ^M^J (CR/LF) である。また、\n は改行文字であって、プログラムでは ^J として読めるが、キー入力のときは ^M、ファイル内では ^M^J の 2 文字で表現される可能性もある。また Mac では ^M が \nである。

C のプログラムで DOS の改行コードを UNIX の改行コードに変換するには、プログラムでそのテキストを読んで、^M^J の並びを ^J に置き換えて出力してやればよい。そのプログラムを DOS 上で作るなら、ファイルの読み書きはバイナリーモードで行う必要がある。または、テキストモードで読んだファイルをそのままバイナリーモードで書き出すだけでもかまわない。


2. C で、行をまたがる文字列定数を書くには?

どうしても行をまたがる定数を書きたければ、

    char *str="ABCDEFGHIJKLM\
         NOPQ...XYZ";

と改行の直前に`\'を入れてやればよい。

また、ANSI C 準拠であれば、

    char *str="ABCDEFGHIJKLM"
         "NOPQ...XYZ";

とすれば、同じ事が出来る。


3. fprintf() の "%d%d%d%d.."を複数行に渡って書きたいのだが。

ANSI Cなら次のように書ける。

    fprintf(fp, "%d%d%d%d%d%d%d%d%d%d"
        "%d%d%d%d%d%d%d%d%d%d"
        "%d%d%d%d%d%d%d%d%d%d"
        ...(途中省略)...
        "%d%d%d%d%d%d%d%d%d%d\n",
        a, b, c, d, .....);

つまり "ABC" は "A" "B" "C" と書いてもかまわず、awk や perl と同様、引用符で囲まれた文字列は、空白 (含む改行)で区切ってある時に連結される。


4. C で、文字列定数領域の生存期間は?

文字列定数領域の生存期間は、プログラムの開始時から終了時として扱ってよい。従って、以下のプログラムの呼び出し元で戻り値を使用するのは可。

char* foo()
{
    return "MESSAGE";
}

自動変数領域の生存期間はその変数が定義されている関数に入ってから出るまでである。従って、以下のプログラムの呼び出し元で戻り値を使用するのは不可。

char *foo()
{
    char mes[10];
    strcpy(mes, "MESSAGE");
    return mes;
}

この場合、以下の様にしなければならない。

char *foo()
{
    static char mes[10];
    strcpy(mes, "MESSAGE");
    return mes;
}

5. MS-DOS で、Ctrl-Z を入力した直後の printf() が出力されない。

main() { printf("abc\032def\nghi\n"); } を実行してみるとわかるが、DOS プロンプトには、「Ctrl-Z 以降行末までを表示しない」というバグ (仕様?) があるようである。("\032" は Ctrl-Z)。

従って、例えば以下のようなプログラムにおいて、

void main(void)
{
    int c;
    c = getchar();
    printf("END1\n");
    printf("END2\n");
}

Ctrl-Zを入力すると、END1 は表示されず、END2 だけが表示されるように見える。ただし、プログラムの出力をファイルにリダイレクトすると、END1 も出力されていることがわかる。


6. malloc()を2回呼んだ時、前とは別に再度メモリーを確保するのか?

malloc は、呼ばれた回数だけバッファの確保を行なう。例えば、確保したバッファ領域を保存するポインタ変数が前と同じ変数である場合でも、そんなことは全く知らずに新たにバッファを獲得する。


7. 長さが不定なファイルを読み込む場合、バッファの扱いとして良い方法はあるか?

1) 事前にファイルサイズを調べて必要量の領域を確保する。ただし、

2) 以下のように、必要に応じて領域を確保する。

ファイル読み込み() {
読み込むサイズの小さめの期待値を確保し、 そのサイズ分だけファイルを読み込む for (;;) { if (指定サイズ未満の量を読んでファイルが終ってしまった) { 成功。 break; } else { // 指定量読めている場合、 領域をrealloc()を使って、今までの倍の領域に広げ、 広がったサイズ分をだけファイルから読み込む。 } } }

8. C で、同じ #define が複数あるのは文法的に誤りか?

ANSI C では、誤りではない。

しかし、コンパイラによっては警告を出すものもあるかもしれない。同じ #define が 2 つあることは全く実害がないことから、仮に警告が出ても無視すれば良いだろう。


9. new で領域を獲得したまま exit した場合、その領域は解放されるか?

まともな OS であれば、プログラムが終了した時点でメモリは解放される。

プログラムを終了したとき、動的にメモリを確保したオブジェクトに対して自動的にデストラクタが呼ばれるかということなら、自動的にデストラクタは呼ばれない。


10. C++ で、auto_ptr とは何か ?

標準 C++ ライブラリでは memory というヘッダファイルに auto_ptr という class template が定義される。これは、ポインタが保持するオブジェクトの所有権の管理を (簡単ながら) 行なってくれる、いわゆるスマートポインタになっている。

例外処理に対応したコンパイラを使う場合は、実行時に例外が throw されて実行するスレッドのスコープが切り替わることがありえる。それを考えると、普通に new で heap 上に確保したオブジェクトへのポインタを生のポインタで受けとって、必要なくなったら delete する様なプログラムの書き方では対応できない (new に対応する delete が実行されない場合がある) ことがありえる。

しかし、生のポインタを auto_ptr でラップするようにすれば、スコープ末尾、例外のどちらで関数を飛び出しても、安全に削除される。同じ事が static 変数にも適用可能である。

auto_ptr は、ANSI 標準になったので ANSI/ISO ドラフトとか、プログラミング言語 C++ 第 3 版には載っている。


11. fork() で子プロセスを生成した後、wait() せずに処理を続けたいのだが。

wait をサボって、zombie プロセスを溜めたくないということなら、

・SystemV で、全ての子プロセスに対して wait しない場合。

親プロセスが、signal(SIGCLD, SIG_IGN) しておけば、子プロセスはゾンビにならない。

・二重に fork を使う

ただし、孫プロセスの wait をしなくていいだけで、子プロセスはしないといけない。

    if((pid=fork())==0) {    /*  vforkでもいい */
        if(fork()==0) {
            exec(............)
        }
        exit(0);
    }
    (void)waitpid(pid);

これで、非同期な waitはしなくてすむ。

・やはりまじめに wait する.

といっても、手間はかからない。

void onCHLD(int i)
{
    while(wait3(NULL,WNOHANG,0)>0)
        ;
}

main()
{ .... signal(SIGCLD, onCHLD);

プログラムの書きかたによっては、システムコールが EINTR で帰る場合の処理を追加する必要があるかもしれない。wait3(), wait4() がない OS ではこの手法は使えないが、そのような場合はたいてい SystemV なので、一番最初の方法で事足りる。


12. 可変引数関数に対して、char、floatの値を引数として渡せるか?

char, float としては渡すことが出来ない。

可変引数の場合、char/short は int に、float は double に変換される。例えば、printf で int 値と char 値を両方とも %d で受けられるのは、双方とも受ける側が int にしか思っていないからである。

ちなみに、可変引数関数に限らず関数に対して char、float、short の値を引数として受け渡すことができないのは K&R 時代の C 言語の仕様であり、ANSI C ではプロトタイプ宣言により時代遅れになったはずなのだが、可変引数関数に限っては互換のため残ってしまっている。また、関数プロトタイプがない関数の引数も同様の扱いを受ける。


13. va_start(va_list ap, last) の第二パラメタには何を渡すのか?

可変引数ではない最後の引数を渡す。

例えば、

void foo(int a, int b, ...)
{
    va_list ap;
    va_start(ap, b);
    ...
}

stdarg では、可変引数リストがどこから始まっているかを明示する必要があり、これを元に、スタック上のどこから引数を取り出し始めるかを決める。


14. 派生クラスのオブジェクトを基底クラスのポインタに入れてる場合、正しいデストラクタが呼ばれるか?

基底クラスのデストラクが仮想関数ならば、正しいデストラクタが呼ばれる (このような使用法をとる場合には、基底クラスのデストラクタを仮想関数にしておくのが鉄則)。

そうでなければ基底クラスのデストラクタが呼ばれる。


15. printf("%d", 42340); とすると、-23196 と表示されると本に書いてあったが?

printf の %d は int の幅でスタックからデータを引いてきて符号付きで文字列にしているので、結果は使っている処理系の int の実装に依存する。

int が 16 ビットの処理系だと、

  %d    -32768(0x8000) 〜 +32767(0x7FFF)
  %u    0(0x0000) 〜 65535(0xFFFF)

int が 32 ビットの処理系だと、

  %d    -2147483648(0x80000000) 〜 +2147483647(0x7FFFFFFF)
  %u    0(0x00000000) 〜 4294967295(0xFFFFFFFF)

という範囲の表示になるので、問題の 42340(0xA564)は、

  16 ビット系  %d : 0xA564 → -23196  MSB は符号 = 1 なので負値
  32 ビット系  %d : 0x0000A564 → 42340 MSB は符号 = 0 なので正値

つまり、16ビットでは負数の範囲になり、その結果 printf で表示される値は -23196 となる。恐らくその本の著者は 16 ビット OS 用の Cコンパイラを用いているのだろう。


16. ポインタが指すものが文字列かどうかを判定する方法はあるか?

ポインタが指すアドレスに特殊性があれば、ひょっとすると文字列と区別できる可能性もあるが、そういう条件を利用して、アドレスか文字列かを判別するようなプログラムが書けたとしても、得るものより失うもののほうが多い。やめたほうが無難。

また、大きな char の配列を用意して文字列はすべてここに詰めて行き、この配列の上限と下限の間の番地にあるポインタを、文字列と判定するという方法も考えられるが、C 言語としては保証されてない。(使えない実装が実存するのかは不明だが)

また、ポインタ値は 4 バイト境界に置く、文字列のほうは (malloc()の結果に +1 するなどして) それ以外のバイト境界に置くという手も考えられる。判別コストが安いかなと思われるが、それでもやっぱり「姑息」感は否めない。

結論としては、一般的には不可能であり、データ種別を示すタグを導入するなどして判定すべきである。


17. #if 0 の働きは?

あるコードが不要になって、コメントアウトする時、使う手段の一つ。手で #if 1 とか書き直さない限りコンパイルされない。

純粋なコメントを書く時にも使えそうに見えるが、

#if 0
    " This string is not terminated
#endif

といった事は出来ないので、注意が必要。

条件コンパイルによってソースコードを代える時にズボラして、

#if 0
    ある条件のコード
#else
    別の条件のコード
#endif

などを書くことがある。

無論...

#ifdef SOME_SETTING
    ある条件のコード
#else
    別の条件のコード
#endif

の方が読みやすいことは間違いない。


18. printf("VAL = %.2lf\n", 0.045); が、0.04 と表示されるのは何故?

ANSI C では、“The value is rounded to the appropriate number of digits.” となっているので、表題の記述では少数点以下第 3 位で四捨五入されると考えてよい。ただし、10進数としてではなく、2進数(16進数) として考える必要がある。

0.045 は 2進数では無限小数なので、計算機内部ではそのままの値ではなく有限桁で表すことが出来る近似値によって表される。

浮動少数点数が IEEE 形式の場合、
  0.045 = 0.04499999999999999833466546306226518936455249786376953125
であり、少数点以下第3位で四捨五入すれば0.04になる。

ちなみに、(float)0.045 = 0.04500000178813934326171875 なので、
  printf("VAL = %.2lf\n", (float)0.045);
の場合は、0.05と表示される。




Index