初級C言語Q&A(online)

Updated: 2003-06-06

[質問一覧] [記事一覧] [ホームページ]


オンラインQ&Aコーナー説明

このページは、メールでいただいた質問に対して、回答をWeb上で行うコーナー です。このコーナーをご利用される方は、以下の事項についてご了承いただくよ うお願い申し上げます。

注意事項


質問と考察 (仮称)

いただいた質問に対して、 何を考えたかというレベルでメモっているので、 それも含めて公開することにしました。

質問と考察 (仮称)トップへ


読者からの質問


Q11 【低水準関数の読み書きで文字化けが起こる】

次のプログラムで、データを作成して保存したファイルの内容を見ると、 文字化けが起きてしまう。 なぜか?

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
 
typedef struct address{
        int code;
        char name[20];
        char add[50];
        char tel[15];
        char mail[50];
}add;
 
int main(){
        add a;
        int flgs =O_TRUNC | O_CREAT | O_RDWR;
        int mode = 00744;
        int i,fd,flg;
        add addtable[10];
        int bufnum;
        int load(add *);
 
        char filename[]="data1";
        if((fd =open("data1",flgs,mode))==-1){
                printf("open error!\n");
                exit(-1);
        }
        printf("1〜10人までのコードを入力できます。(終了時はコード0)\n");
        for(i=1;i<11;i++){
  /* 途中省略 */
                write(fd,&a.code,sizeof(int));
                write(fd,a.name,20);
                write(fd , a.add , 50);
                write(fd,a.tel,15);
                write(fd,a.mail,50);
  /* 途中省略 */
        }
        close(fd);
  /* 途中省略 */

}
 
int load(add *pt){
        int fd;
        int i;
        char filename[20];
 
        printf("保存したファイルを開きます。\n");
        printf("開きたいファイル名を指定してください\n");
        scanf("%s" , filename);
        fd = open("filename", O_RDONLY);
        read(fd , pt , sizeof(add)*10 - 1);
        for(i = 0 ; i < 10 ; i++){
                printf("%d\n" , (pt+i)->code);
                printf("%s\n" , (pt+i)->name);
                printf("%s\n" , (pt+i)->add);
                printf("%s\n" , (pt+i)->tel);
                printf("%s\n" , (pt+i)->mail);
 
        }
        close(fd);
 
}

read や write を使って構造体の内容を読み書きする場合は、 メンバ毎に情報を書き込んだら読み込む時もメンバ毎に処理し、 構造体全体をまるごと書き込んだら読み込む時もまるごと一度に処理する必要があります。

元のプログラムは、 構造体のメンバ単位に書き込む処理を行っていますが、 書き込んだ内容をまるごと読み込もうとしているので、 それがトラブルの原因になっている可能性があります。

但し、元のプログラムにはバグが多すぎるため、 実際に何がご指摘の「文字化け」の原因になっているか定かではありません。 失礼ながら、 プログラムを拝見した限りでは、 プログラミングの技術が低水準関数を扱うレベルに達していないと思われますので、 基本から復習してみた方がよいのではないかと思います。

(考察の途中経過は、 質問と考察 (11) に書いてあります。)


Q10 【構造体のメンバの値をセットする】

次のような構造体がある。

struct color {
    int red, green, blue, kido;
};

struct zahyou {
    int x,y;
};

struct gazou {
    struct color akarusa;
    struct zahyou zahyou1;
};

これに対して、red、green、blue の値が与えられた状態で、 kido にそれらの平均値をセットするにはどうすればよいか?
(ペンネーム jun さん)

kido 以外の値をセットした状態から、 kido もセットした状態にしたいという趣旨でしょう。 例えば、

    struct gazou Image = {{20,40,90},{240,180}};

という状態から Image.akarusa.kido をセットするのであれば、

    Image.akarusa.kido = (Image.akarusa.red + Image.akarusa.green + Image.akarusa.blue) / 3;

とすればよいのですが、 変数が出てくる毎にこのような計算式を書くのは非現実的なので、 このような場合は構造体へのポインタを渡して関数内で処理するとよいでしょう。

void set_kido(struct gazou *p)
{
    p->akarusa.kido = (p->akarusa.red + p->akarusa.green + p->akarusa.blue) / 3;
}

このような関数を作っておけば、 Image.akarusa.kido のセットは、次のような関数呼び出しで済みます。

set_kido(&Image);

(考察の途中経過は、 質問と考察 (10) に書いてあります。)


Q9 【ポインタのintへのキャスト】

構造体へのポインタをintにキャストして、その結果を(元と同じ型の)構造体へのポインタにキャストした場合に値は保証されるのか。 即ち、次のような処理は正しいか。
また、このようにして得たiを保存しておいて、 後から読み込んで利用することはできるか。

    struct sample *a;
    int i;
    
    …
    i = (int) a;
    …
    a = (struct sample *) i;

JIS X 3010 によれば、 「ポインタは、汎整数型に型変換してよい。 要求される整数の長さ及び結果は、処理系定義とする。 領域の大きさが不十分な場合、その定義は未定義とする。」 「任意の整数は、ポインタに型変換してよい。結果は、処理系定義とする。」 という規格になっています。 従って、このような変換結果がどうなるかは処理系に依存することになります。

但し、一般的には、 ポインタの値を汎用的に保持したい場合には void へのポインタを用います。

    struct sample *a;
    void *p;
    
    p = a;
    a = p;

この結果のaの値は、最初と同じであることが保証されます。

仮にポインタをintにキャストした結果が情報を正しく維持しているとしても、 ポインタは他のオブジェクトの位置を示しているだけなので、 ポインタが指しているオブジェクトが同じ所にある保証がなければ ポインタの値を保持する意味はありません。 つまり、ポインタではなくポインタの指す先を保存しなければ意味がありません。 保存した状態を復元するには、保存したオブジェクトを復帰させて、 そこを指すように新たにポインタの値を設定し直します。 このようにすれば、ポインタの値は保存前と同じである保証はありませんが、 ポインタの指している先に同じものがあることは保証できます。

Q8 【スタイルの規約】

 「C言語」の統一された、もしくは規格化された書式体系はあるか? 例えば"for( i = 0 ; i < MAX ; i++ )"のように空白を入れなければならない、というようなものを意味する。

 C言語はフリースタイルですから、言語規格としては書き方は自由です。空白を入れなければ識別できないような場合を除けば、どのように書いても構いません。 しかし、現実問題として、書式が読み手に影響を与えることは確かです。基本的には、書式をどうするかということより、同一のプロジェクトやソースファイルの中で書式が統一されていることの方が重要だと思われます。 そこで、プロジェクトによっては統一された書式を推奨することがあります。例えば有名なものとして、 GNUコーディング規則 があります。

Emacsのように、エディタの機能として、プログラムを書く時に自動的にインデントなどを付けてくれるものもあり、コーディングの規則もカスタマイズできるものもあります。

Q7 【多数のデータの表示】

 100個の実数のデータを10行10列の形式でfprintfで表示するにはどうしたらよいか?
(From: dai さん)

 データが配列に入っているなら、単純にループで処理すれば一つずつ表示できます。

    for (i = 0; i > 100; i++) {
        printf("%e\n", a[i]);
    }

 これだと1つのデータごとに改行しますから、10回に一度改行するようにすれば目的の結果は得られます。

    for (i = 0; i > 100; i++) {
        printf("%e", a[i]);
        if ((i % 10) == 9) {
            printf("\n");
        }
    }

 データの数に応じて、改行するタイミングを適当に調節すればよいと思います。幅を一定にして表示したい場合は、printfの書式指定の所で桁数や精度を指定して試してください。

Q6 【サイズ不明のファイルを配列に読み込む】

 プログラム中でファイルの内容を配列に読み込みたいが、ファイルの大きさが事前に分らないため配列のサイズを正確に予測できない。mallocを使ってメモリを動的に割り当てるにはどうすればよいか。

 mallocで最初に適当なサイズを確保しておき、足りなくなったらreallocを使ってサイズを変更するというのが最も安易な方法です。

 次のコードは、fread関数が処理に成功した場合に、まだファイルに読み残しがある場合には必ず指定サイズ読み込まれていること、かつmalloc、reallocに必ず成功すること、という前提で書いてあります。

    char *p_top; /* 配列の先頭になるポインタ */
    char *p; /* 作業用 */
    size_t n; /* freadの戻り値 */
    size_t size; /* 読み込んだサイズ */

    p_top = malloc(SIZE); /* 配列の先頭 */
    size = SIZE; 
    p = p_top; /* 読み込む位置 */

    while ((n = fread(p, 1, SIZE, pfp)) == SIZE) {
        p_top = realloc(p_top, size + SIZE); /* サイズを SIZEだけ増やす */
        p = p_top + size; /* 次に読む位置 */
    }

    if (n > 0) {
      p_top = realloc(p_top, size - SIZE + n); /* 余分に取った所をカット */
    }

 実用に使うためには、fread関数が失敗した時の処理、mallocやreallocが失敗した時の処理が必要です。細かいですが、ファイルの大きさが0バイトの時の処理も考えた方がよいでしょう。これらの処理は前述のサンプルに追加するのではなく、mallocやreallocに代替する関数を一段追加する方が簡単です。例えば、mallocの場合は次のような関数my_mallocを作っておき、前述のような処理からはmy_mallocを呼ぶことにします。

void *my_malloc(size_t size)
{
    void *p;

    p = malloc(size);
    if (p == NULL) {
        fprintf(stderr, "メモリが足りません\n");
        exit(1); /* 異常終了したことにする */
    }
    return p;
}

 freadも、常にsizeだけ読み込むようなmy_freadを考えてみてください。なお、通常のファイルを読む場合には、正常に処理できた場合には常にsizeだけ読まれているというようなシステムも多いようです。

 標準ライブラリではありませんが、システムによってはstatというシステムコールを使ってファイルの大きさを調べる事もできます。

Q5 【signedとunsigned】

 signed int と unsigned int はどのように使い分けるとよいか?

 まず、当たり前ですが、符号が必要な計算にはsignedを使います。

 それ以外の場合としては、unsignedにしておけばオーバーフローが発生しないことがポイントになります。C言語の仕様ではsignedの値を計算してオーバーフローした場合の結果は未定義ですから、例えばそれでプログラムが停止するかもしれません。 signedの値を使う場合にオーバーフローしないようにするのはプログラマーの義務ということになります。もちろん、オーバーフローが発生するのであれば、その値はおそらくそのまま使うには問題があるはずです。


Q4 【関数の配列】

 次のような関数を、関数への配列を使って実現するにはどうすればよいか。
int foo(int i)
{
    if(0 == i)
        return func0();
    if(1 == i)
        return func1();
    if(2 == i)
        return func2();
    if(3 == i)
        return func3();
    return -1;
}

 関数へのポインタ型をtypedefで定義しておけば、分かりやすくなります。
typedef int (*int_fp)(); /* int型の関数へのポインタ型 */

int func0(void);
int func1(void);
int func2(void);
int func3(void);

int_fp function_array[4] = {func0, func1, func2, func3};

int foo(int i)
{
    if (i >= 0 && i <= 3) {
        return function_array[i]();
    }
    return -1;
}
 この場合、function_array[i]が関数型int_fpを持ちますので、()を付けて呼 び出せば関数を呼ぶことができます。

Q3 【long intのオーバーフロー】

次のコードに現れる式 P + 1は何を意味しているのか。
static long random_x = 1L;
static const long P = 0x7FFFFFFFL;

/*
 * 線形合同法による乱数の生成
 *
 * 戻り値:0から2の31乗−1までの数の一つを返す(と思われる)。
 */
long get_random2(void)
{
    const long a = 48828125;
    const long b = 19;

    random_x = a * random_x + b;
    if (random_x < 0)
        random_x += P + 1;

    return random_x;
}

longが32ビットであると仮定します。Pの型は符号付きのlong intですから、 0x7FFFFFFFLというのは最大の正の値となります。 これに1を加える式、P + 1はlongの範囲を超えてオーバーフローを引き起こすため、 その結果はC言語では未定義です。 ただし、このコードは符号ビットが1の時にそれを0にすることを期待しているよ うです。

unsigned longの計算はオーバーフローを引き起こさず、結果は2の32乗を法とす る算術法則に従うことが仕様で定められています。従って、unsigned longに置 き換えることによって、コードから未定義の箇所を取り除くことができます。 おそらく、多くの処理系で、次のコードは元のコードと同一の結果を得ると思わ れます。

static unsigned long random_x = 1UL;

long get_random2(void)
{
    const unsigned long a = 48828125UL;
    const unsigned long b = 19UL;

    random_x = a * random_x + b;
    random_x &= 0x7FFFFFFFUL;
    return (long) random_x;
}

Q2 【VC++4.0とQuick Win】

VC++4.0にはVC++1.0のようなQuick Winはないのか?

VC++を持っていないため分からなかったのですが、NIFTY-ServeのC言語フォーラ ムで質問してみたところ、「ない」というコメントをいただきました。VC++4.0は、 Windowsベースのコーディングを想定しているのだと思われます。

Q1 【comp.lang.cの読み方】

 comp.lang.cは一日で100あまりの発言があるのですが、どうしたら効率よく読 めるのでしょうか?
(From: Hideki Amano <amano@aimnet.or.jp> さん)

 効率よく読むというのは難問になると思います。世の中にはニュースリーダー と呼ばれている専用ソフトがいくつかあるので、模範的回答としては、このよう なソフトを使ってまずSubjectの一覧を表示し、興味のある投稿のみを表示して、 まず本文の最初10行程度を読み、あるいはざっと見て価値が高そうなら全部を読 む、という手順を繰り返す、ということになるでしょう。

 ただし、私の場合は、ニュースリーダーを使っていません。

 まず一気に未読の投稿をファイルにセーブします。環境にもよりますが、これ はアクセス料金や電話代を節約する効果もあります。手元には例えば、clc.tmp というファイルができます。これをVz Editorのような高速のエディタを使って、 速読:-)します。

 読んだものは、別のファイルに移動します。この処理は、エディタを使ってい るので簡単です。全投稿を1ファイルにできれば検索が楽なのですが、システムの 能力等が追いつかないので、最近は500投稿毎に一つのファイルにしています。一 つのファイルは約1MB前後のサイズになります。例えば、clc.010だと、1000〜 1499番の発言が入るという感じになっています。99999番を越えるとちょっと困り ますが、多分、別のディレクトリを用意して対応すると思います。

 こうやって、順番に見るという操作の繰り返しなのですが、意外と効率的かも しれません。


※ c.l.c FAQ : comp.lang.c FAQ list

http://www.eskimo.com/~scs/C-faq.top.html
文中の項目番号は新しい版に対応しています。旧版とは異なります。

(C) 1998 Phinloda, All rights reserved
無断でこのページへのリンクを貼ることを承諾します。問い合わせは不要です
内容は予告なく変更することがあります。