C言語

提供: Wikinote
2011年2月10日 (木) 01:15時点におけるHagio (トーク | 投稿記録)による版 (三項演算子)

(差分) ←前の版 | 最新版 (差分) | 次の版→ (差分)
移動: 案内検索

C 言語 最高 再考。

参考リンク

覚え書き

キャストの桁あふれ動作

実装依存な気もするから、C 言語のページに書くのが妥当かどうかは不明だが…

キャスト時に桁あふれが生じていた場合はどうなるのか。

#include <stdio.h>
int main() {
    int i;
    unsigned long l;
    unsigned long long ll = 0xFFFFFFFFUL;
    for (i = 0; i < 3; i++, ll++) {
        l = (unsigned long) ll;
        printf("ll = %llu, l = %lu\n", ll, l);
    }
    return 0;
}

こうなる。

ll = 4294967295, l = 4294967295
ll = 4294967296, l = 0
ll = 4294967297, l = 1

下位 32 ビットのみが代入されるってことか?とりあえずアセンブラを見てみる。

16     movl    $-1, -16(%ebp)  // ll の下位 32 ビット確保
17     movl    $0, -12(%ebp)   // ll の上位 32 ビット確保
18     movl    $0, -24(%ebp)   // i 確保
19     jmp .L2
20 .L3:
21     movl    -16(%ebp), %eax // ll の下位 32 ビットを eax へ
22     movl    %eax, -20(%ebp) // eax の内容で l 確保 ★
23     movl    -20(%ebp), %eax // 意味ねー
24     movl    %eax, 12(%esp)  // l を積む
25     movl    -16(%ebp), %eax // ll の下位 32 ビットを eax へ
26     movl    -12(%ebp), %edx // ll の上位 32 ビットを edx へ
27     movl    %eax, 4(%esp)   // ll の上位 32 ビットを積む
28     movl    %edx, 8(%esp)   // ll の下位 32 ビットを積む
29     movl    $.LC0, (%esp)   // フォーマット文字列のアドレスを積む
30     call    printf
31     addl    $1, -24(%ebp)   // i++
32     addl    $1, -16(%ebp)   // 下位 ll++
33     adcl    $0, -12(%ebp)   // 上位 ll + 0 (下位からのキャリー付き)
34 .L2:
35     cmpl    $2, -24(%ebp)   // for の条件判定
36     jle .L3

やはり、下位 32 ビットのみを l に代入していた。

scanf() の桁あふれ動作

例えば以下のようなコードがあって

    long l;
    unsigned long ul;
      :
    while (fgets(line, 128, fp) != NULL) {
        sscanf(line, "%ld %lu", &l, &ul);
        printf("l = %ld, ul = %lu\n", l, ul);
    }

fp の指すテキストファイルが以下の内容だった場合どうなるのか。

1 1
2147483647 4294967295
2147483648 4294967296
2147483649 4294967297
-1 -1
-2 -2
-3 -3
-2147483647 -4294967295
-2147483648 -4294967296
-2147483649 -4294967297

こうなる。

l = 1, ul = 1
l = 2147483647, ul = 4294967295
l = 2147483647, ul = 4294967295
l = 2147483647, ul = 4294967295
l = -1, ul = 4294967295
l = -2, ul = 4294967294
l = -3, ul = 4294967293
l = -2147483647, ul = 1
l = -2147483648, ul = 4294967295
l = -2147483648, ul = 4294967295

とりあえず、パッと見でわかるのは以下のこと。

  • singed では、最小値・最大値で頭打ちになる。
  • unsigned では、最大値で頭打ちになる。

キャスト時の挙動と違っていてややこしい…。

三項演算子

カーネルソース中に、以下のような三項演算子がよく出てくる。

int res = 0, hoge = 10, fuga = 20;
res = hoge ? : fuga;

このとき res の値は、

res = 10

つまり、hoge の値になる。

可変引き数リスト

#include <stdarg.h>
void myprintf(char *fmt, ...) {
  va_list ap;
  int ival;
  double dval;
  char *sval;

  va_start(ap, fmt);
  // 取り出す前に、型がわかっている必要がある。
  ival = va_arg(ap, int);
  dval = va_arg(ap, double);
  sval = va_arg(ap, char *);
  :
  va_end(ap);
}


構造体のサイズ

以下のような構造体のサイズはいくつになるだろうか?

#include <stdio.h>

struct {
    int i;
    short s1;
    char c;
    short s2;
} A;

struct {
    char c1;
    int i;
    char c2;
    short s;
} B;

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

こうなる。

$ ./a.out 
sizeof(A) = 12, sizeof(B) = 12

なぜなら、型の大きさに(?)アラインされ、パディングが入るから。 例えば、4 バイトの int なら、アドレスは必ず 4 の倍数になる。

Alignment.png


関数ポインタ

いつまでたっても覚えられない。なにしろ使わないからなあ…。

int (*func)(int x);   // int を引き数にとり、int を返す関数へのポインタ

これはひどい。(K&R P149)

char (*(*x())[])()
    x: function returning pointer to array[] of
    pointer to function returning char

文字列を返す関数へのポインタの配列へのポインタを返す関数だって!!! こんなのが出てきた時点で、プログラムの設計ミスだろ…。


printf のフォーマット

% で始まり、変換指定子で終わる。以下のプログラムで実験。

#include <stdio.h>

int main(void) {
    float f;

    printf("1234567890 1234567890\n");
    printf("---------------------\n");
    f = 123.456;
    printf("%f %f\n", f, -f);               // デフォルト
    printf("%.2f %.2f\n", f, -f);           // 小数点以下桁 (精度) 指定
    printf("%10f %10f\n", f, -f);           // 最小幅指定
    printf("%10.2f %10.2f\n", f, -f);
    printf("%010.2f %010.2f\n", f, -f);     // 0 で埋める
    printf("%-10.2f %-10.2f\n", f, -f);     // 左揃え
    printf("%+10.2f %+10.2f\n", f, -f);     // 必ず符号をつける
    printf("%*.2f %*.2f\n", 15, f, 15, -f); // 引数による幅指定
    printf("%*3$.2f %*3$.2f\n", f, -f, 20); // 引数による幅指定 (引数指定)
    printf("%.*3$f %.*3$f\n", f, -f, 10);   // 引数による精度指定 (引数指定)

    return 0;
}

実行結果は下記のようになる。

$ gcc -o printf printf.c; ./printf
1234567890 1234567890
---------------------
123.456001 -123.456001
123.46 -123.46
123.456001 -123.456001
    123.46    -123.46
0000123.46 -000123.46
123.46     -123.46   
   +123.46    -123.46
         123.46         -123.46
              123.46              -123.46
123.4560012817 -123.4560012817


定数

最大値、最小値は 64 ビット環境 (Snow Leopard) でのもの。

定数の型 最小値 最大値
char 'a'
signed char -128 127
unsigned char 0 255
short -32768 32767
unsigned short 0 65535
int 12345 -2,147,483,648 2,147,483,647
unsigned int 12345U 0 4,294,967,295
long 123456789L -9,223,372,036,854,775,808 9,223,372,036,854,775,807
unsigned long 123456789UL 0 18,446,744,073,709,551,615
float 123.456F
double 123.456
long double 123.456L
8 進数 0123
16 進数 0x335F
16 進数 unsigned long 0x12ABUL
8 進数ビットパターン '\013'
16 進数ビットパターン '\x1A'
文字列定数 "hello, world"

※大文字、小文字の区別はない。(U, L, X はそれぞれ u, l, x でも同じ。)

  • 列挙定数 (enum) の初期値は 0。
  • 文字列定数はコンパイル時に連結できるので、以下のような書き方も可能である。長い文章を出力するときなどに便利。OSS のソースを見ても、\ で折り返してるケースが多いが、こちらの方がインデントなどなにかと使いやすいように思える。
printf("aaaaaaaaaaaaaaa"
       "bbbbbbbbbbbbbbb"
       "ccccccccccccccc\n");
// print "aaaaaaaaaaaaaaabbbbbbbbbbbbbbbccccccccccccccc\n"


マクロ

引数付きマクロ
#define max(A, B) ((A) > (B) ? (A) : (B))
#define square(x) (x) * (x) // 括弧が重要: square(z+1) を考えよ!

ただし、以下のような使い方はできないことに注意すること。

max(i++, j++); // 大きな方が 2 度インクリメントされる
引用符付き文字列展開 (#)
#define debug_print(var) printf(#var " = %g\n", var)

これは、以下のように展開される。

debug_print(x);
⇒ printf("x" " = %g\n", x);
⇒ printf("x = %g\n", x);    // 文字列は連結される。
実引数連結 (##)
#define joint(prefix, suffix) prefix ## suffix

これは、以下のように展開される。

joint(hoge, 3)
⇒ hoge3


その他 (細かいこと)

  • argc は引き数無しで 1 である。(めっちゃ忘れやすい)
  • #define 行の末尾にはセミコロンはいらない
  • extern宣言であって、定義ではない。つまり、領域の確保などは行われない。別ファイルで定義される変数などを利用する際に使う。疑問:ヘッダファイル中で extern 宣言した場合、定義するファイルからそのヘッダを include してもよいのか。⇒ よい (少なくとも、エラーや警告は出ない。)

テクニック

下位 8 ビットマスク

~ (1 の補数) やシフト演算子をうまく使うことで、ビット処理を行う。

x = x & ~0xFF;

配列の初期化

K&R には、以下の記述がある。

配列の初期値式が指定された数より少ないときには、外部変数、静的変数、自動変数については、残りの要素は 0 となる。

つまり、大きな配列を 0 で初期化したい場合、自動変数であっても以下のようにできるということである。

int bigarray[10000] = {0};

ほんとか!? ⇒ ほんと。次のように、要素数 0 での初期化も可能であった。

int bigarray[10000] = {};

標準ライブラリ

  • assert.h (診断機能)
    • void assert(int expression)
      • expression が 0 のとき、プロセスを停止する。
  • complex.h (複素数計算)
  • ctype.h (文字操作)
  • errno.h (エラー)
    • ライブラリ関数内でエラーが発生した場合、そのエラーの内容を報告するために使用するいくつかのマクロ定義。
  • float.h (浮動小数点型の特性)
    • 浮動小数点型の大きさや様々な特性を表すマクロの定義。
  • limit.h (整数型の大きさ)
  • locale.h (文化圏固有操作)
  • math.h (数学)
  • setjmp.h (非局所分岐)
  • signal.h (シグナル操作)
  • stdarg.h (可変個数の実引数)
  • stddef.h (共通の定義)
  • stdio.h (入出力) <toggledisplay>
    • void clearerr(FILE *stream)
    • int fclose(FILE *stream)
    • int feof(FILE *stream)
    • int ferror(FILE *stream)
    • int fflush(FILE *stream)
    • int fgetc(FILE *stream)
    • int fgetpos(FILE *stream, fpos_t *pos)
    • char *fgets(char *s, int n, FILE *stream)
    • FILE *fopen(const char *filename, const char *mode)
    • int fprintf(FILE *stream, const char *format, ...)
    • int fputc(int c, FILE *stream)
    • int fputs(const char *s, FILE *stream)
    • int fputs(const char *s, FILE *stream)
    • FILE *freopen(const char *filename, const char *mode, FILE *stream)
    • int fscanf(FILE *stream, const char *format, ...)
    • int fseek(FILE *stream, long offset, int whence)
    • int fsetpos(FILE *stream, const fpos_t *pos)
    • long ftell(FILE *stream)
    • size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
    • int getc(FILE *stream)
    • int getchar(void)
    • char *gets(char *s)
    • void perror(const char *s)
    • int printf(const char *format, ...)
    • int putc(int c, FILE *stream)
    • int putchar(int c)
    • int puts(const char *s)
    • int remove(const char *filename)
    • int rename(const char *old, const char *new)
    • void rewind(FILE *stream)
    • int scanf(const char *format, ...)
    • void setbuf(FILE *stream, char *buf)
    • int setvbuf(FILE *stream, char *buf, int mode, size_t size)
    • int sprintf(char *s, const char *format, ...)
    • int sscanf(const char *s, const char *format, ...)
    • FILE *tmpfile(void)
    • char *tmpnam(char *s)
    • int ungetc(int c, FILE *stream)
    • int vfprintf(FILE *stream, const char *format, va_list arg)
    • int vprintf(const char *format, va_list arg)
    • int vsprintf(char *s, const char *format, va_list arg)</toggledisplay>
  • stdlib.h (一般ユーティリティ) <toggledisplay>
    • double atof(const char *s)
    • atoi(const char *s)
    • atol(const char *s)
    • double strtod(const char *s, char **endp)
    • long strtol(const char *s, char **endp, int base)
    • unsigned long strtoul(const char *s, char **endp, int base)
    • int rand(void)
    • void stand(unsigned int seed)
    • void *calloc(size_t nobj, size_t size)
    • void *malloc(size_t size)
    • void *realloc(void *p, size_t size)
    • void free(void *p)
    • void abort(void)
    • exit(int status)
    • atexit(void (*fcn)(void))
    • int system(const char *s)
    • char *getenv(const char *name)
    • void *bsearch(const void *key, ...)
    • void qsort(void *base, ...)
    • int abs(int n)
    • long labs(long n)
    • div_t div(int num, int denom)
    • ldiv_t ldiv(long num, long denom)</toggledisplay>
  • string.h (文字列操作)
  • time.h (日付及び時間)