2012.07.10
yna

C言語の落とし穴(1)

[1] C言語は高級アセンブラ?

C言語はUNIXオペレーティングシステムを汎用的に移植できるようにするために、作成された言語です。このためOSに近いレベルやRubyやPerlといったスクリプト言語を作るレベルでは広く使われています。
このため口の悪い人には、高級言語ではなくて、高級アセンブラだとか、低級言語と言われることも少なくありません。
Cの処理系では、一般的な言語やスクリプト言語では当たり前に備えている機能が無かったり、ユーザーに任されているところがあります。

メリット

  • 生成されたコードの実行速度が速い
  • 生成されたコードが小さい

デメリット

  • 文字列すら型に存在しない
  • ポインターが理解できないと、配列ひとつ操作できない。
  • メモリー管理が煩雑

[2] 型とポインターについて

2.1.文字列すらない?

「C言語には文字列という型はありません。」というと、あの有名なコードを示して、ちゃんと” Hello World”って文字列があるじゃないと言われそうです。

void    main()
{
    printf("Hello World");
}

でもこの文字列は確かに文字列のように見えますが、実際には文字型配列の一種なのです。文字型の配列をまるで文字列として扱っているだけなのです。証拠というわけではありませんが、ちょっと変わったプログラムをお見せしましょう。

void main()
{
    char        *str = "Hello world";
    str+= 1;
    printf(str);
}

このプログラムの実行結果は、以下のようになります。

ello world

2.2.変数には型がある

さてPHPやperlスクリプト系の言語を使っているとあまり意識しないのですが、C言語のデータや変数には型があります。
まずは、基本になる型でこれをプリミティブ型といい、C言語には以下のような型があります。

char 整数型
short int
int
long int
float 不動小数点型
double
void 値が無いという型

ちょっと、変な気もするでしょうが、charは文字型ではなく、整数型のひとつとして扱われ、8ビット整数になっています。
short intとlong intは一般にintを省略して、shortとlongと表記します。32ビット環境では、intとlongは32ビット整数、shortは16ビット整数、。(64ビット環境はちょっと判りません)。
floatとdoubleはそれぞれ浮動小数点型という実数を扱う型です。
最後にvoidという型というか概念があります。voidは値を持たないという意味になります。値を持たないため、変数の型として使われことはありません。さらに、void型のデータを記述することもできません。普通は関数の型に書いて、「この関数は値を返さないよ」と宣言するときに使用します

さらに整数型に付くunsignedとsigned(普通は符号付なのでほとんど使われません)という修飾子があります。

unsigned char sigend char
unsigned short sigend short
unsigned int sigend int
unsigned long signed long

2.3.文字の話

C言語では文字はシングルコーテーション(‘)で囲みます。’A’と書くと、普通の言語では文字のAですが、C言語は、65という数値になります(ASCIIコード系の場合)。

void	main()
{
	int	n;
	n = 'A';
	printf("%d", n );
}

ここで、文字Aを出力するには、printfの書式文字列”%d”を”%c”にします。
一方、ダブルコーテーションマーク(“)で囲むと、最後に0を入れた文字の配列と見做されます。Cでは文字型配列の要素が0だと、それを文字列の終了記号と見做して処理します。
“Hello world”は、実際にはこんな感じでメモリー中に入っています。

(数字で表記した場合)

72 101 108 108 111 32 119 111 114 108 100 0

(文字で表記した場合)

‘H’ ‘e’ ‘l’ ‘l’ ‘o’ ‘ ‘ ‘W’ ‘o’ ‘r’ ‘l’ ‘d’ ‘\0’

2.4.ポインター

さて、C言語で肝になる概念のひとつポインターの説明です。
C言語は全ての型(あとで説明する構造体も含めて)に、派生する型であるポインターがあります。C言語ではポインターというのは、特定の型を示すアドレス型データのです。(ポインターとアドレスが違うという説明をする人もいますが、ポインターとは、アドレスに示している先のデータの型を加味して拡張した考え方だと思って差し支えありません)
以下のように、変数名の前に*をつけて宣言します。ポインターのポインターも宣言することができます。最後のvoid型へのポインターは、どんなデータを示しているかという情報が無くなったポインターです。(アドレスとほぼ一緒)

char        *str;
int         *ptr;
double      *pNum;
char        **argv;
void        *pVoid;

ポインター説明

変数の位置を取得する(=リファレンスを取得する)には、変数名の直前に&演算子をつけます。一方ポインター変数からその示している位置のデータを取得するには、*演算子を変数のあたまにつけます。

char        *str;
char        cc;
    cc = 'A';
    str = &cc;
    putchar(*str);      // Aが表示される

最もよく出てくる文字のポインターから説明します。

void    main()
{
    char    *str;
    str = "Hello world" ; 
    while( *str){
        putchar( *str );
        str++;
    }
}

    str = "Hello world" ;

まず、ポインター型の変数に”Hello World”のアドレスを入れます。

    while( *str){

次に、*strでそのポインタの示している位置の文字を評価します。0でないので、ループを実行します

        putchar( *str );

strの示している箇所の数字を、文字として表示します。(putcharは、文字コード渡して、1文字出力する関数です)

        str++;

str++で、ポインターを一個進めます。charだとサイズ1バイトなので、strの指す位置は1001番になります。


str++で、ポインターを一個進めます。charだとサイズ1バイトなので、strの指す位置は1001番になります。

この処理を繰り返して最後に、ポインターは0を指します。*strは0を返しますので、ループは終了します。

void型のポインター(示している先が型の無いアドレス)は、ほぼアドレスと同じと考えていいでしょう。あらゆるポインターは、void型のポインターに変換することが出来ますが、逆はエラーになります。(ただしキャストという方法で強制的に変換することができます。)

例えば、文字列のポインターを整数のポインターにキャストすると、直接値を見ることができます。お行儀のいい方法ではないですが、こんなことができます。

void    test04()
{
    char        *str;
    int         *ptr ;
    str = "Hello world" ;
    ptr = (int *)str;
    printf("%d\n", *ptr );
    ptr++;
    printf("%d\n", *ptr );
    ptr++;
    printf("%d\n", *ptr );
    ptr++;
}


ptr++をすると、intのサイズは32ビット=4バイトなので、4進んで1004番を示すようになります。

2.5.変数の初期化

char        cc;
char        *str;
    // この時点で変数ccとstrには何が入っているでしょうか?
    cc = 'A';
    str = &cc;

変数宣言が必要な言語では、宣言しただけの変数はNULLになっていたり、0で初期化されていることが多いのですが、Cでは初期化はされません。コメントの位置での変数ccとstrの値は何が入っているかわからないというのが本当です。
間違って値を設定しないまま変数を使用してしまうと危険です。今ではこのような書き方をするのが一般的です。

char        cc = 'A';
char        *ptr = &cc;
char        *str = "ABC";

int     aryList[] = { 0, 1, 2, 3 }; //  個数4個の初期化付き配列

さて、結構な分量を書きましたので、次回に持ち越したいと思います。
(yna)

一覧に戻る