C言語について
三つのバージョンをサポートしている。ただし最新バージョンのサポートは
完全ではない。
※
ANSI C標準またはC89 、時にはC90
1990年にISO(国際標準化機構)がANSI CをISO C90規格として制定したものである。
※
C95
小規模な改訂が行われてISO C95規格が作られた。
※
C99
1999年に言語仕様の改定や複素数型,複素数関数の導入などが追加された。しかしGCCはC99規格を100%サポートしていない。
※ C99規格について
・boolについて
C言語では今までboolという型は標準ではなかったstdbool.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
例(test2.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
例(test5.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 ?pedanticはC89でコンパイルする設定である
・
サイズ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 ?pedanticはC89でコンパイルする設定である
・
複素数について
あまり意味のない例だが複素関数“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 ?pedanticはC89でコンパイルする設定である
例(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で仮定して処理中、2が入っていて悩むこともある。そういうアホなバグに悩むことはなくなる。
ゼロからビット操作しようが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