C プログラム Tips

エンディアンチェック

int型や long型,float型などの複数バイトのデータをメモリに格納する場合,上位バイトから格納するか (Big endian),下位バイトから格納するか (Little endian)は,プロセッサによって異なる.このようなバイト並びを仮定したプログラムは移植性がない.

次のプログラムはエンディアンをチェックするプログラムです. このプログラムを実行するプロセッサが Big endianLittle endian かを表示します.

#include <stdio.h>

int main(void)
{
    unsigned int i;
    int ival;
    long long lval;
    char *cp;

    int endian;
    union {
        long l;
        char c[sizeof (long)];
    } u;

    ival = 0x01020304;
    lval = 0x0102030405060708LL;

    cp = (char *)&ival;
    printf("int val = %08x\n", ival);
    for (i = 0; i < sizeof(int); i++) {
        printf("%02x ", cp[i]);
    }
    puts("");

    cp = (char *)&lval;
    printf("long long val = %08llx\n", lval);
    for (i = 0; i < sizeof(long long); i++) {
        printf("%02x ", cp[i]);
    }
    puts("\n");

    /* Are we little or big endian? */
    u.l = 1;
    endian = (u.c[sizeof (long) - 1] == 1);
    printf("This computer is ");
    if (endian == 0) {
        printf("'Little endian'");
    } else {
        printf("'Big endian'");
    }
    printf(" machine.\n");

    return 0;
}

ネットワークバイトオーダ

ネットワークを使った数値の通信においては,バイトオーダ(上位バイトから送信/受信するか下位バイトから送信/受信するか)が送受信双方で異なっていると正しく通信できないため,あらかじめバイトオーダを決めておく必要がある(ネットワークバイトオーダという). TCP/IP プロトコルでは,ネットワークバイトオーダは MSB から先に送る Big endian となっている.したがって,Little endian のマシンで通信する場合,変換が必要になる.

ネットワークバイトオーダの変換関数として htons() と htonl() が,その逆は ntohs() と ntohl() が用意されている.htons は host to network short の略というような命名規則である.

次のプログラムは,変換関数によるバイトオーダ変換の確認プログラムです.

#include <stdio.h>
#include <arpa/inet.h>

int main(void) {
    unsigned int val = 0xAABBCCDD;
    unsigned char *pc = (unsigned char *)&val;

    printf("%X : %X : %X : %X\n" , pc[0] , pc[1] , pc[2] , pc[3]);
    val = htonl(val);
    printf("%X : %X : %X : %X\n" , pc[0] , pc[1] , pc[2] , pc[3]);
    val = ntohl(val);
    printf("%X : %X : %X : %X\n" , pc[0] , pc[1] , pc[2] , pc[3]);

    return 0;
}

これを実行すると次のような結果が得られます(リトルエンディアン環境).

  DD : CC : BB : AA
  AA : BB : CC : DD
  DD : CC : BB : AA

プログラム領域アドレス

Cプログラムは一般的には以下のように5つの領域から構成されている.

・TEXTセグメント コード領域
・DATAセグメント 初期化データ領域
・BSSセグメント 非初期化データ領域
・ヒープ領域 動的確保領域
・スタック領域 スタック領域

各セグメントの並びはアドレスの低番地から上記の順になっている.但し,処理系によってはスタック領域とヒープ領域が逆になる場合がある.

次のプログラムは上記のアドレスを確認するプログラムです.

#include <stdio.h>
#include <stdlib.h>

int ex1;
int exi = 1;
int ex2;
static int es;
static int esi = 1;

int main(void)
{
	int a;
	static int s;
	char *p, *q;

	a = s = ex1 = ex2 = 0;
	p = "test data";
	q = (char *)malloc(100);

	printf("p:   char const   = %p\n", p);
	printf("exi: extern(init) = %p\n", &exi);
	printf("esi: static(init) = %p\n", &esi);

	printf("s:   static       = %p\n", &s);
	printf("es:  static       = %p\n", &es);
	printf("ex2: extern       = %p\n", &ex2);
	printf("ex1: extern       = %p\n", &ex1);

	printf("q:   malloc       = %px\n", q);
	printf("a:   auto         = %px\n", &a);

	free(q);
	return 0;
}

これを実行すると次のような結果が得られます.

  p:   char const   = 0x400824
  exi: extern(init) = 0x601054
  esi: static(init) = 0x601050
  s:   static       = 0x601070
  es:  static       = 0x601074
  ex2: extern       = 0x60107c
  ex1: extern       = 0x601078
  q:   malloc       = 0x6f0010x
  a:   auto         = 0x7fffd13df6e0x

変数型とサイズ

C言語では,変数を使用するのに変数の型を指定するが,ANSI C 標準では正確なサイズは定義されていない.すなわち,実行する環境により,変数が実際に使用する領域サイズは異なる.

プログラムの移植性を高めるためには,変数のサイズを仮定したコードを書かないようにする必要がある.C言語には,変数や型のサイズを調べられる sizeof 演算子が用意されている. 指定された型や変数のデータサイズがバイト単位で返される.

次のプログラムは,sizeof 演算子を使って変数型やポインタのサイズを確認するプログラムです.

#include <stdio.h>

int main(void)
{
    printf("char  = %ld\n", sizeof(char));
    printf("short = %ld\n", sizeof(short));
    printf("int   = %ld\n", sizeof(int));
    printf("long  = %ld\n", sizeof(long));
    printf("long long = %ld\n", sizeof(long long));
    printf("float = %ld\n", sizeof(float));
    printf("double = %ld\n", sizeof(double));
    printf("char* = %ld\n", sizeof(char *));

    return 0;
}

これを実行すると次のような結果が得られます(64ビット環境).

  char  = 1
  short = 2
  int   = 4
  long  = 8
  long long = 8
  float = 4
  double = 8
  char* = 8
inserted by FC2 system