トップC言語入門 > 配列と文字列、sizeof演算子

配列と文字列、sizeof演算子

1.プログラム例

// array01.c

#include <stdio.h>

int main() {
    int data[] = { 23, 78, 11, 99, -1 };
    int n;
    for (n = 0; data[n] > 0; n++) printf(" %d", data[n]);
    printf("\n");
    for (n = 0; n < sizeof(data)/sizeof(int); n++) printf(" %d", data[n]);
    printf("\n");
}
c:\MH\www\c01\array>array01
 23 78 11 99
 23 78 11 99 -1

2.sizeof 演算子

int 型配列 data の要素数は Java では data.length で得られるが、 C言語では sizeof演算子を使って、sizeof(data) / sizeof(int) (またはsizeof(data) / sizeof(data[0])) とする。 sizeof演算子は変数名またはデータ型をオペランドとする。 byte単位のサイズが得られる。

なお、n < sizeof(data) / sizeof(int)のように書いたからといって、実行時に毎回割り算が実行されるわけではない。 コンパイル段階で、sizeof(data) の値は確定しており、sizeof(int) も32ビット(4バイト)パソコンならば 4 という定数であり、定数計算が行われ、上のプログラムでは、n < 5 と書いたのと同じ機械語コードが生成される。 したがって、実行時間が長くなるようなことはない。

配列の要素の終わりは、末尾に特別な値(正の値の配列では -1、 文字列配列では NULL)を置いて、この出現をチェックする方法も しばしば採用される。

上のプログラム例では、sizeof は関数のように見えるが、関数ではなく、前置単項演算子である。 char, char *, int などデータ型名は関数の引数にはできない。 演算子であることから sizeof data と書いてもよいが、sizeof(data) のように、被演算子を () で括ることが多い。 () で括らないとエラーとなるコンパイラもある。 演算子であるが、関数だと思っても、一般には特に問題はない。

3.char配列と文字列ポインタの違い[2014.12.27]

char *p = "12"; と char p[] = "12"; では p に微妙な違いがある。 前者は文字列を指すポインタ変数であり、後者は文字(char型)の配列の先頭番地である。

次のプログラム array03.c を gcc, cl(MSVC), tcc でコンパイルしてみよう。

#include <stdio.h>

int main() {
    char *p  = "12";
    char q[] = "12";

    p[0] = 'a';
    q[0] = 'a';
    printf("p=%s\n", p);
    printf("q=%s\n", q);
    return 0;
}

いずれもコンパイルエラーは起きない。

cl および tcc で得られた array03.exe の実行結果を下の通りである。

c:\sys>array03
p=a2
q=a2

一方、gccで得られた a.exe では実行エラーとなる。

char *p = "12"; と char p[] = "12"; の違いは何かを考えて頂きたい。 基本的なことではあるが、完璧に理解している人は少ないのではなかろうか? 分かりにくいのはその言語の欠点だと思う。

char *p = "12"; の場合、文字列 "12" は、一般には、定数文字列領域に置かれる。 この定数領域は、システムおよびコンパイラに依存するが、通常は、書き込み禁止領域となる。

p は文字列 "12" のアドレスを表す。従って、この文字列が書き込み禁止領域にあれば、 p[0] = 'a' で実行時エラーが起きて当然である。

しかし、定数文字列領域が書き込み禁止でないか、または、 コンパイラが書き込み禁止領域から、書き込み可能領域(変数領域)にコピーして、 p がこの領域にある文字列を指しているならば、エラーは起きない。

一方、char q[] = "12"; の場合には、char q[3] = { '1', '2', '\0' }; と書いたのと同じであり、 q はスタック領域上のchar配列の先頭アドレスである。 従って、当然書き込み可能であるから、q[0] = 'a'; には何の問題もない。

const char *p = "12"; とはしていないのであるから、C言語仕様上は、gccに問題がある、 と言うべきかも知れないが、紛らわしさがあるため、 書き換えが起きない場合は char *p = "12"; とし、 書き換えが起きるときは char p[] = "12"; とすべきである。

書き換え禁止のときは本来は const char *p = "12"; とべきである。 こうすれば、gcc, cl, tcc いずれのコンパイラ でも、コンパイルエラーが出る。

ただ、変数宣言や仮引数に、 いちいち const を付けると、プログラムの簡潔さが失われるため、私自身は、ついつい、省略しがちである。