コメントの形式

 C++ではコメントを次のように記述します.

  #include <stdio.h>
  //typedef short t_id; //コメント行
  int foo(t_id x);
  int main(void)

 もちろんオプションに-ansiまたは-traditionalを指定した場合には,C++方式のコメントは認識されません.ほかの多くのCの実装でもこのようなコメントを使うことができるので,ソースをANSIに限定しなくてよいのなら,このコメント形式を使って問題ないでしょう(リスト7).

〔リスト7〕C++方式のコメントを使用したソース(test88.c)
#include <stdio.h>
//コメント
typedef short   t_id;
int foo(t_id x);
int main(void)
{
    int result;
    t_id y;
    printf("start\n");
    y   =   2;
    result = foo(y);
    printf("%d\n",result);
    return 0;
}
	
int foo(t_id x)
{
    return x * x;
}

 -ansiオプションでコンパイルした場合,次のようにエラーとなります.

  $ gcc -S -ansi test88.c
  test88.c:2: parse error before '/'
  test88.c:4: parse error before 'x'
  test88.c: In function 'main':
  test88.c:8: 't_id' undeclared (first use in this function)
  test88.c:8: (Each undeclared identifier is reported only once
  test88.c:8: for each function it appears in.)
  test88.c:8: parse error before 'y'
  test88.c:10: 'y' undeclared (first use in this function)
  test88.c: At top level:
  test88.c:16: parse error before 'x'
  test88.c: In function 'foo':
  test88.c:18: 'x' undeclared (first use in this function)
  $

識別子の名前の中のドル記号

 GNU Cでは識別子の名前の中でドル記号($)を使うことができます.もちろん古いCコンパイラでもそのような識別子を使うことを許しているものもあります.しかし,識別子の中のドル記号がサポートされないターゲットマシンもあります.その理由として,ターゲットマシンのアセンブラが識別子の中のドル記号を許さないことがあるからです.

 そのようなターゲットマシン用にクロスコンパイルをする場合には,オプション-pedanticをつけてコンパイルするとエラーになるのでわかりやすいでしょう(リスト8).

〔リスト8〕識別子の名前にドル記号を使用したソースtest89.c)
#include <stdio.h>
typedef short   t_id;
int foo(t_id x);
int main(void)
{
    int $result;
    t_id y;
    printf("start\n");
    y   =   2;
    $result = foo(y);
    printf("%d\n",$result);
    return 0;
}
	
int foo(t_id x)
{
    return x * x;
}

 -pedanticオプションでコンパイルした場合,次のようにエラーとなります.なお連載第3回(本誌2002年10月号)の「警告を要求/抑止するオプション」でpedanticオプションについて説明しています.

  $ gcc -S -pedantic test89.c
  test89.c:6: warning: '$' in identifier
  test89.c:10: warning: '$' in identifier
  test89.c:11: warning: '$' in identifier
  test89.c: In function 'main':
  test89.c:6: warning: '$' in identifier
  test89.c:10: warning: '$' in identifier
  test89.c:11: warning: '$' in identifier
  $

ESC文字について

 メールの文字列中などで,[ESC]文字と$Bや(Bなどの文字を組み合わせた「エスケープシーケンス」で,文字セットを切り替えています.JIS-1983規格に切り替える際には[ESC] $ Bを文字列中に埋め込みます.

 そのような用途に使う場合に,文字列リテラルとして“\e$B”のように定義ができます(リスト9).

〔リスト9〕ESC文字を使用したソースtest90.c)
#include <stdio.h>
int main(void)
{
    printf("\e$B");
    printf("\e$(O");
    return 0;
}

  $ gcc -S -pedantic test90.c
  test90.c: In function 'main':
  test90.c:4: warning: non-ANSI-standard escape sequence, '\e'
  test90.c:5: warning: non-ANSI-standard escape sequence, '\e'
  $

 拡張機能を使用した場合にワーニングを出す指定をすると,上記のようになります.

型あるいは変数のアラインメントを問い合わせる

 たとえば,sizeofを使う場合と同様に__alignof__で,あるオブジェクトがどのようにアラインメントされるかを問い合わせることができます.実行環境によっては,アラインメントを必要としないものもあります.そのような場合に__alignof__は型の推奨アラインメントを報告します.

 __alignof__のオペランドがlvalueの場合には,__alignof__の値はそのlvalueが取るとわかっているアラインメントの値のうち最大の値となります.

 このアラインメントの値は,そのlvalueのデータ型から決められることもありますし,そのlvalueが構造体の一部である場合には,その構造体からアラインメントの値を継承することもあります(リスト10リスト11).

 上記のソースの実行結果は,次のようになります.

  $ gcc -o test91 test91.c
  $ ./test91
  doubleの境界----8
  floatの境界----4
  long longの境界----8
  intの境界----4
  charの境界----1
  fooの境界----8
  foo.eの境界----1
  $

 おそらく身近にあるインテルまたはインテル互換ではない環境,たとえばMac OS X上のGCCでは,また違った結果になると思います.

 ちなみに第5回連載で作成したSHプロセッサ向けの環境でコンパイルすると,次のようになります(リスト12).

〔リスト12〕test91.cをSHプロセッサ用にコンパイルした際のアセンブラSH_test91.s)
    .file   "test.c"
    .data
gcc2_compiled.:
___gnu_compiled_c:
    .text
    .align 2
LC0:
    .ascii "double\244\316\266\255\263\246----%d\12\0"
    .align 2
LC1:
    .ascii "float\244\316\266\255\263\246----%d\12\0"
    .align 2
LC2:
    .ascii "long long\244\316\266\255\263\246----%d\12\0"
    .align 2
LC3:
    .ascii "int\244\316\266\255\263\246----%d\12\0"
    .align 2
LC4:
    .ascii "char\244\316\266\255\263\246----%d\12\0"
    .align 2
LC5:
    .ascii "foo\244\316\266\255\263\246----%d\12\0"
    .align 2
LC6:
    .ascii "foo.e\244\316\266\255\263\246----%d\12\0"
    .align 2
    .global _main
_main:
    mov.l   r8,@-r15
    mov.l   L3,r1
    mov.l   r14,@-r15
    sts.l   pr,@-r15
    jsr     @r1
    mov     r15,r14
    mov.l   L4,r8
    mov.l   L5,r4
    jsr     @r8
    mov     #4,r5
    mov.l   L6,r4
    jsr     @r8
    mov     #4,r5
    mov.l   L7,r4
    jsr     @r8
    mov     #4,r5
    mov.l   L8,r4
    jsr     @r8
    mov     #4,r5
    mov.l   L9,r4
    jsr     @r8
    mov     #1,r5
    mov.l   L10,r4
    jsr     @r8
    mov     #4,r5
    mov.l   L11,r4
    jsr     @r8
    mov     #1,r5
    mov     #0,r0
    mov     r14,r15
    lds.l   @r15+,pr
    mov.l   @r15+,r14
    rts
    mov.l   @r15+,r8
L12:
    .align 2
L3:
    .long   ___main
L4:
    .long   _printf
L5:
    .long   LC0
L6:
    .long   LC1
L7:
    .long   LC2
L8:
    .long   LC3
L9:
    .long   LC4
L10:
    .long   LC5
L11:
    .long   LC6

  $ sh-hitachi-coff-gcc -O3 -S test91.c

 上記のようにchar,foo.eは1バイト,その他は4バイト境界になっています.

変数の属性の指定

 キーワード__attribute__により,変数または構造体フィールドに特別な属性を指定することができます.このキーワードの後に,二重の丸括弧(())に囲まれた属性指定が続きます.現在,8個の属性aligned,mode,nocommon,packed,section,transparent_union,unused,weakが変数に対して定義されています.その他の属性が,前述の関数および後述する型に対して定義されています.

 複数の属性を指定するには,たとえば“__attribute__ ((aligned (16), packed))”のように,2重の丸括弧(())の中で属性をカンマで区切ります.

 それぞれのキーワードの前後に__を付けて属性を指定することもできます.もし同じ名前をもつマクロが定義済みでも,ヘッダファイルの中でキーワードを使うことができるようになります.たとえば,alignedの代わりに__aligned__を使うことができます.

○aligned (alignment)

 この属性は,変数または構造体フィールドで最小のアラインメントの値をバイト単位で指定します.以下のソースはそれぞれアラインメントを16バイト,32バイトにしたものです.

 オブジェクトファイルのシンボルをnmコマンドで出力してみます.すると,リスト13リスト16のようにfoo1のアラインメントが変わっています.

 なお,aligned属性の指定においてアラインメントの係数を省略した場合,コンパイルのターゲットマシン上においてもっとも効率のよい値をコンパイラが自動的に設定します.インテルx86アーキテクチャの場合は,リスト17のようにintの場合は4が設定されます.

〔リスト17〕変数のアラインメントを自動的に設定したソースtest94.c)
#include <stdio.h>
//変数属性の指定 aligned (alignment) 
struct foo { int x[20] __attribute__ ((aligned )); }foo1;
int main(void)
{
    foo1.x[0]   =   1;
    foo1.x[1]   =   2;
    printf("foo1の境界----%d\n",__alignof__(foo1));
    return 0;
}

 以下は実行結果です.

  $ gcc -o test94 test94.c
  $ ./test94
  foo1の境界----4
  $

○mode (mode)

 この属性は,宣言のデータ型を指定します.指定される型は,モードmodeに対応する型です.byteを指定すれば1バイトになります.wordを指定すればその環境の1ワード,pointerを指定すればその環境で使用できるポインタの長さが確保されます(リスト18).

〔リスト18〕mode属性で変数の長さを変更しているソース(test95.c)
#include <stdio.h>
//変数属性の指定 mode (mode) 
int main(void)
{
    char x __attribute__ ((mode(pointer) ));
    int y __attribute__ ((mode(byte) ));
    printf("xのサイズ----%d\n",__alignof__(x));
    printf("yのサイズ----%d\n",__alignof__(y));
    return 0;
}

 実行結果は次のようになります.

  $ gcc -o test95 test95.c
  $ ./test95
  xのサイズ----4
  yのサイズ----1
  $

 リスト18ではchar型にポインタの長さを,int型に1バイトを指定しています.プロセッサの環境が変わってもポインタの長さを正確に確保したり,1ワードの大きさを正確に確保する場合に有効な方法ですが,GCCに慣れていない人には理解できないでしょう.

○nocommon

 この属性を指定するとグローバル変数の場合,.bssセクションに配置します(リスト19リスト20).なお,その変数は初期化されます.

○packed

 packed属性は,aligned属性が指定されていない限り,変数または構造体フィールドが,アラインメントを可能な限り最小の値にすることを指定します.この最小値は,変数については1バイトであり,フィールドについては1ビットです(リスト21リスト22).

 実行結果は次のようになります.

  $ ./test97
  foo1のサイズ----9
  foo1.xのサイズ----8
  foo1_のサイズ----12
  foo1_.xのサイズ----8
  $

○section (“section-name”)

 コンパイルの際に,通常は生成するオブジェクトをdataやbssといったセクションに置きます.しかし,場合によっては,たとえば特殊なハードウェアにマップするために,追加のセクションが必要になったり,特定の変数を特殊なセクションに置くことが必要になります.

 section属性は,ある変数が,ある特定のセクション内に存在するよう指定します(リスト23リスト24).

 このように,それぞれfoo1_section,foo2_section,data_sectionに置かれています.

 オブジェクト上ではリスト25のように配置されます.

〔リスト25〕section属性を指定したソースをリンクしたオブジェクト配置リストtest98nm.txt)
08048304 t Letext
080494ac ? _DYNAMIC
08049490 ? _GLOBAL_OFFSET_TABLE_
08048450 R _IO_stdin_used
08049484 ? __CTOR_END__
08049480 ? __CTOR_LIST__
0804948c ? __DTOR_END__
08049488 ? __DTOR_LIST__
0804947c ? __EH_FRAME_BEGIN__
0804947c ? __FRAME_END__
0804954c A __bss_start
08049454 D __data_start
         w __deregister_frame_info@@GLIBC_2.0
080483f0 t __do_global_ctors_aux
08048330 t __do_global_dtors_aux
         w __gmon_start__
         U __libc_start_main@@GLIBC_2.0
         w __register_frame_info@@GLIBC_2.0
08049478 A __start_data_section
08049460 A __start_foo1_section
0804946c A __start_foo2_section
0804947c A __stop_data_section
0804946c A __stop_foo1_section
08049478 A __stop_foo2_section
0804954c A _edata
08049564 A _end
08048430 ? _fini
0804844c R _fp_hw
08048274 ? _init
080482e0 T _start
08048304 t call_gmon_start
0804945c d completed.4
08049478 ? data
08049454 W data_start
08048384 t fini_dummy
0804946c ? foo2_
08049460 ? foo_
08049460 d force_to_data
08049460 d force_to_data
08048390 t frame_dummy
08048304 t gcc2_compiled.
08048330 t gcc2_compiled.
080483f0 t gcc2_compiled.
08048430 t gcc2_compiled.
080483d0 t gcc2_compiled.
080483b8 t init_dummy
08048418 t init_dummy
080483d0 T main
0804954c b object.11
08049458 d p.3

○transparent_union

 この属性は共用体型の関数パラメータに対して指定されます.そのパラメータに対応する引き数自体は,その共用体の任意のメンバの型をもつことができます.しかし,その引き数がその関数に渡される際には,共用体の1番目のメンバの型をもつものとして扱われます.詳細については,

型属性の指定で説明します.

○unused

 この属性は変数に対して指定されます,その変数はおそらく使われないはずであるということを意味します.GNU Cは,その変数については警告メッセージを出力しません.

○weak

 weak属性については,第7回(本誌2003年3月号)の連載にある関数属性の宣言で説明しています.

○model (model-name)

 model属性はオブジェクトがスモール/ミディアム/ラージであると宣言しなくてはならない環境において,それを宣言するものです.

Copyright 2003 岸 哲夫

Copyright 1997-2024 CQ Publishing Co.,Ltd.

 

NEW記事内インデックス    連載インデックスはこちら   Interfaceのトップ
プロトタイプ宣言と古い方式の関数定義
◆コメントの形式/識別子の名前の中のドル記号/ESC文字について/型あるいは変数のアラインメントを問い合わせる/変数の属性の指定
型属性の指定/Cの式をオペランドとしてもつアセンブラ命令
アセンブラコードの中で使われる名前の制御/指定されたレジスタの中の変数/代替キーワード/不完全なenum型/関数名の文字列/関数の復帰アドレスとフレームアドレスの獲得