C言語について

三つのバージョンをサポートしている。ただし最新バージョンのサポートは

完全ではない。

     ANSI C標準またはC89 、時にはC90

1990年にISO(国際標準化機構)がANSI CISO C90規格として制定したものである。

     C95

小規模な改訂が行われてISO C95規格が作られた。

     C99

1999年に言語仕様の改定や複素数型,複素数関数の導入などが追加された。しかしGCCC99規格を100%サポートしていない。

     C99規格について

boolについて

C言語では今までboolという型は標準ではなかったstdbool.hをインクルードすれば使用することができる

例(test1.c

#include <stdio.h>

#include <stdbool.h>  // stdbool.hヘッダーファイル

 

bool isless5(char c) {

  return '0' <= c && c < '5';

}

 

int main(int argc, char **argv) {

  char c = '4';

  if (isless5(c)) {

    printf("%c is less 5 \n", c);

    printf("\n");

  }

}

コンパイルおよび実行結果

kishi@kishi-VirtualBox:~$ gcc test1.c -o test1

kishi@kishi-VirtualBox:~$ ./test1

4 is less 5

例(test.c#include <stdbool.h>を削除

#include <stdio.h>

 

bool isless5(char c) {

  return '0' <= c && c < '5';

}

 

int main(int argc, char **argv) {

  char c = '4';

  if (isless5(c)) {

    printf("%c is less 5 \n", c);

    printf("\n");

  }

}

コンパイルおよび実行結果

kishi@kishi-VirtualBox:~$ gcc test2.c -o test2

test2.c:3:1: エラー: 不明な型名 bool です

(test3.c))型名をboolから_Boolに変更

#include <stdio.h>

 

_Bool isless5(char c) {

  return '0' <= c && c < '5';

}

 

int main(int argc, char **argv) {

  char c = '4';

  if (isless5(c)) {

    printf("%c is less 5 \n", c);

    printf("\n");

  }

}

コンパイルおよび実行結果

kishi@kishi-VirtualBox:~$ gcc test3.c -o test3

kishi@kishi-VirtualBox:~$ ./test3

4 is less 5

可変長配列について

   C99には可変長配列が導入された、しかし初期化の際に可変長配列を作れるだけで初期化以降は可変長の設定はできない。

(test4.c)) 1024*1024*1024=1073741824の可変長配列を作る

#include <stdio.h>

 

long fsize(int s) {

  char tbl[s*1024*1024];

  return sizeof tbl;

}

 

int main(int argc, char **argv) {

  printf("%ld\n", fsize(1024));

}

コンパイルおよび実行結果

kishi@kishi-VirtualBox:~$ gcc test4.c -o test4

kishi@kishi-VirtualBox:~$ ./test4

1073741824

(test.c)) test4.cとソースは同じ

コンパイルおよび実行結果

kishi@kishi-VirtualBox:~$ gcc test5.c -std=c89 -pedantic -o test5

test5.c: 関数 fsize :

test5.c:4:3: 警告: ISO C90 は可変長の配列 tbl を禁止しています [-Wvla]

オプション-std=c89 ?pedanticC89でコンパイルする設定である

     サイズ0配列について

ANSI-Cでは、Cの配列の長さの最小値は「1」である。このため、例えば頭に長さを持った可変長のデータ構造を、構造体を使って作成する場合、サイズ「1」を指定する必要があった。

C99では構造体の最後の要素が配列の場合、サイズの省略が可能だ。

(test6.c)配列の初期サイズはゼロ

#include <stdio.h>

#include <stdlib.h>

 

#define test_SIZE 1024

 

struct test

{

  int nakami[0];

};

 

int main()

{

  struct test t;

  struct test* tp;

  int i;

 

  printf("test size : %d 初期サイズ\n", sizeof(t));

 

  tp = (struct test*) malloc(sizeof(int) * test_SIZE);

 

  for (i = 0; i < test_SIZE; ++i){

    tp->nakami[i] = 0;

  }

 

  return 0;

}

コンパイルおよび実行結果

kishi@kishi-VirtualBox:~$ gcc test6.c -o test6

kishi@kishi-VirtualBox:~$ ./test6

test size : 0 初期サイズ

(test6.c)コンパイルオプション変更

コンパイルおよび実行結果

kishi@kishi-VirtualBox:~$ gcc test6.c -std=c89 -pedantic -o test6

test6.c:8:7: 警告: ISO C はサイズが 0 の配列 nakami を禁止しています [-pedantic]

オプション-std=c89 ?pedanticC89でコンパイルする設定である

     複素数について

あまり意味のない例だが複素関数“cexp”を使ってみる。コンパイル時にオプション-lmを使うこと。

(test7.c)

#include <stdio.h>

#include <complex.h>

#include <math.h>

 

int main(void){

   double _Complex z, x;

 

   x=I*M_PI;

   z=cexp(x);

   z=z*M_PI+2j;

 

   printf("real=%f\timag=%f\n", creal(z),cimag(z));

 

   return 0;

 }

コンパイルおよび実行結果

kishi@kishi-VirtualBox:~$ gcc test7.c -o test7 -lm

kishi@kishi-VirtualBox:~$ ./test7

real=-3.141593  imag=2.000000

(test7.c)コンパイルオプション変更

コンパイルおよび実行結果

kishi@kishi-VirtualBox:~$ gcc test7.c -std=c89 -pedantic -o test7 -lm

test7.c: 関数 main :

test7.c:6:11: 警告: ISO C90 は複素数型をサポートしません [-pedantic]

test7.c:8:8: エラー: M_PI が宣言されていません (この関数内での最初の使用)

test7.c:8:8: 備考: 未宣言の識別子は出現した各関数内で一回のみ報告されます

test7.c:10:13: 警告: 虚数定数は GCC 拡張です [デフォルトで有効]

オプション-std=c89 ?pedanticC89でコンパイルする設定である

(test8,c)

//何の意味もない例だが”//”コメントもc99では有効になる

#include<stdio.h>

int main(void){

  for(int i=0;i<10;i++)

    printf("%d\n",i);

  return(0);

}

上記の拡張分はGCC独自の文法拡張なのか、今回の定義はオプション-std=gnu99 ?pedanticを付けないとエラーになってしまう

コンパイルおよび実行結果(C99モード)

kishi@kishi-VirtualBox:~$ gcc test8.c -o test8 -std=gnu99 -pedantic

kishi@kishi-VirtualBox:~$ ./test8

0

1

2

3

4

5

6

7

8

9

同ソースでC89モードでコンパイル

kishi@kishi-VirtualBox:~$ gcc test8.c -o test8 -std=c89 -pedantic

test8.c:1:1: エラー: expected identifier or ( before / token

test8.c:1:1: エラー: プログラム内に逸脱した \344 があります

(いくつかコメント関連のエラーメッセージ)

test8.c:1:1: エラー: プログラム内に逸脱した \213 があります

In file included from /usr/include/stdio.h:75:0,

                 from test8.c:2:

/usr/include/libio.h:334:3: エラー: 不明な型名 size_t です

/usr/include/libio.h:338:67: エラー: size_t がここでは宣言されていません (関数内ではない)

/usr/include/libio.h:366:62: エラー: expected declaration specifiers or ... before size_t

/usr/include/libio.h:375:6: エラー: expected declaration specifiers or ... before size_t

/usr/include/libio.h:497:19: エラー: expected =, ,, ;, asm or __attribute__ before _IO_sgetn

In file included from test8.c:2:0:

/usr/include/stdio.h:338:20: エラー: expected declaration specifiers or ... before size_t

/usr/include/stdio.h:706:15: エラー: expected =, ,, ;, asm or __attribute__ before fread

/usr/include/stdio.h:712:15: エラー: expected =, ,, ;, asm or __attribute__ before fwrite

test8.c: 関数 main :

test8.c:4:3: エラー: for ループ初期化宣言は C99 モード内でのみ許可されています

test8.c:4:3: 備考: オプション -std=c99 または -std=gnu99 をコードコンパイル時に使用してください

コメントもエラーになってしまった。

     inline関数およびlonglong int

オプションに何もつけないと、コンパイルは通る。

(test9.c)

#include<stdio.h>

//inline関数が使用できる

inline double square(double a){

  return(a*a);

}

int main(void){

  double k=5.2;

  printf("square(%f)=%f\n",k,square(k));

//long long int 型をブロックの途中で宣言する

  long long int xx=9223372036854775807LL;

//long long int を印字

  printf( "long long int: %lld\n", xx );

  return(0);

}

コンパイルおよび実行結果

kishi@kishi-VirtualBox:~$ gcc test9.c -o test9

kishi@kishi-VirtualBox:~$ ./test9

square(5.200000)=27.040000

long long int: 9223372036854775807

kishi@kishi-VirtualBox:~$ gcc test9.c -o test9

kishi@kishi-VirtualBox:~$ ./test9

square(5.200000)=27.040000

long long int: 9223372036854775807

同ソースでC89モードでコンパイル

kishi@kishi-VirtualBox:~$ gcc test9.c -o test9 -std=c89 -pedantic

test9.c:4:1: エラー: expected identifier or ( before / token

(コメント関連のエラー)

test9.c:4:1: エラー: プログラム内に逸脱した \213 があります

test9.c: 関数 main :

test9.c:10:3: 警告: 書式 %f は引数の型が double であると予期されますが、第 3 引数の型は int です [-Wformat]

test9.c:11:1: エラー: expected expression before / token

(コメント関連のエラー)

test9.c:12:20: 警告: C99 long long 整数定数を使用しています [-Wlong-long]

test9.c:13:1: エラー: expected expression before / token

(コメント関連のエラー)

     inline関数について

inline関数はマクロのようにソースの該当箇所に埋め込まれるが、マクロと違い引数の型チェックがあるので安全に使うことができる。またGDBによるデバッグ時にはインライン化は共有ルーチンへジャンプする変わりに、関数の呼び出しがあった位置に直接関数本体のコピーを挿入する。

関数の呼び出しはA関数を呼び出す際に呼び出し元の環境、呼び出し先の環境に実引数b、仮引数などの環境を作り実行する。つまりその行動がある程度の時間を要するのでinline関数であれば関数呼び出しのオーバーヘッドが無いのである。ただし、組込みシステムなどシビアなコードの大きさの削減を行いたい場合にはやらないほうが良いと思う。

     restrictについて

ポインターが同じ場所を指していることもあるかもしれない。

メモリー操作関連のライブラリ変数の定義を見てみよう。

kishi@kishi-VirtualBox:~$ cat /usr/include/string.h |grep memcpy

extern void *memcpy (void *__restrict __dest,

kishi@kishi-VirtualBox:~$ cat /usr/include/string.h |grep memmove

extern void *memmove (void *__dest, __const void *__src, size_t __n)

    上記のようにmemcpyはエイリアスは無いと仮定しているので内部コードに変換すると高速に動作する。

     bool ( _Bool )について

Bool 型の変数は、値が 0 1 のいずれかにしかならない。シフトしようが0.01を代入しようが値は1になるゼロを代入しない限りゼロにならない。良くしょうもないバグでスイッチが0か1で仮定して処理中、が入っていて悩むこともある。そういうアホなバグに悩むことはなくなる。

ゼロからビット操作しようが1から演算しようが、取りうる値はゼロか1である。

 

 

 

     手動最適化の入り口

     どのようにアセンブラソースを吐き出すか?

試験用に簡単なソースを用意する

(test1-1.c)

int f()
{
    int a = 100;
    int b = 200;
    return a-b;
}
上のソースからアセンブラを作成するためには
gcc ?S test1-1.c
出来上がったアセンブラソースは次のようなものだ。
test1-1.s
        .file   "test1-1.c"
        .text
        .globl  f
        .type   f, @function
f:
.LFB0:
        .cfi_startproc
        pushl   %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        subl    $16, %esp
        movl    $100, -8(%ebp)
        movl    $200, -4(%ebp)
        movl    -4(%ebp), %eax
        movl    -8(%ebp), %edx
        movl    %edx, %ecx
        subl    %eax, %ecx
        movl    %ecx, %eax
        leave
        .cfi_restore 5
        .cfi_def_cfa 4, 4
        ret
        .cfi_endproc
.LFE0:
        .size   f, .-f
        .ident  "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
        .section        .note.GNU-stack,"",@progbits
 

このアセンブラソースを見る限りデフォルトではint型変数はスタックされ、レジスタに入らない.

次に意図的にレジスター変数を使う。

test1-2.c

int f()

{

    register int a = 100;

    register int b = 200;

    return a-b;

}

上のソースからアセンブラを作成するためには
gcc ?S test1-2.c
出来上がったアセンブラソースは次のようなものだ。

test1-2.s

        .file   "test1-2.c"

        .text

        .globl  f

        .type   f, @function

f:

.LFB0:

        .cfi_startproc

        pushl   %ebp

        .cfi_def_cfa_offset 8

        .cfi_offset 5, -8

        movl    %esp, %ebp

        .cfi_def_cfa_register 5

        pushl   %esi

        pushl   %ebx

        movl    $100, %esi

        .cfi_offset 3, -16

        .cfi_offset 6, -12

        movl    $200, %ebx

        movl    %esi, %eax

        subl    %ebx, %eax

        popl    %ebx

        .cfi_restore 3

        popl    %esi

        .cfi_restore 6

        popl    %ebp

        .cfi_def_cfa 4, 4

        .cfi_restore 5

        ret

        .cfi_endproc

.LFE0:

        .size   f, .-f

        .ident  "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"

        .section        .note.GNU-stack,"",@progbits

数値はレジスタに格納されている。

しかしCPUに依存するので注意は必要だ。

グローバル変数,局所変数のどちらにも使用可能だ。

またますますCPUに依存するが、どのレジスタに入れるか指定することも可能だ。

test1-3.c

int f()

{

  register int          a asm("ecx")    =    100;

  register int          b asm("edx")    =    200;

  return a-b;

}

上のソースでは定数100を“ecx”定数200を“edx”に入れている。

              test1-3.s

        .file   "test1-3.c"

        .text

        .globl  f

        .type   f, @function

f:

.LFB0:

        .cfi_startproc

        pushl   %ebp

        .cfi_def_cfa_offset 8

        .cfi_offset 5, -8

        movl    %esp, %ebp

        .cfi_def_cfa_register 5

        movl    $100, %ecx

        movl    $200, %edx

        movl    %edx, %eax

        movl    %ecx, %edx

        subl    %eax, %edx

        movl    %edx, %eax

        popl    %ebp

        .cfi_def_cfa 4, 4

        .cfi_restore 5

        ret

        .cfi_endproc

.LFE0:

        .size   f, .-f

        .ident  "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"

        .section        .note.GNU-stack,"",@progbits