「C言語」の版間の差分

提供: Wikinote
移動: 案内検索
(覚え書き)
(構造体のサイズ)
行27: 行27:
 
  sizeof(A) = 12, sizeof(B) = 12
 
  sizeof(A) = 12, sizeof(B) = 12
 
なぜなら、型の大きさに(?)アラインされ、パディングが入るから。
 
なぜなら、型の大きさに(?)アラインされ、パディングが入るから。
 +
例えば、4 バイトの int なら、アドレスは必ず 4 の倍数になる。
 +
[[画像:alignment.png]]
  
 
=== 関数ポインタ ===
 
=== 関数ポインタ ===

2009年3月11日 (水) 00:04時点における版

C 言語 最高 再考。

覚え書き

構造体のサイズ

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

#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

定数

定数の型
long 123456789L
unsigned 123U
unsigned long 123456789UL
double 123.456
unsigned double 123.456L
float 123.456F
8 進数 0123
16 進数 0x335F
16 進数 unsigned long 0x12ABUL
文字定数 'a'
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

その他 (細かいこと)

  • #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 (入出力)
    • 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)
  • stdlib.h (一般ユーティリティ)
  • string.h (文字列操作)
  • time.h (日付及び時間)