To Home
Last modified: Wed May 20 07:03:12 JST 2009

C/C++

C/C++の規格をちょこっと読んでみたい今日この頃
ここにあるソースコードは正しいとは限りません。 突っ込み、励まし大歓迎です。(メールは address へ)
注: このページの情報を鵜呑みにしてはいけません.ちゃんと確かめましょう

内容

いろいろ

構造体の代入演算子によるコピー

  整数、ポインタ、浮動小数点数などで配列でない変数はload,storeなどの命令で 1つずつコピーを行っている。配列を構造体に含むときは、構造体のサイズがわ かっているので構造体のサイズ分だけmemcpyを呼び出してコピーしている。もちろん 値渡しなので、ポインタ(参照)では渡していない。

共用体

  どれか一つの型のデータしか保持しない。
  共用体のサンプルプログラム(union.cc)

関数へのポインタ

  関数へのポインタの宣言は次の通り
   [return_type] (*[pointer_name])([arg_types])
e.g.)
int (*fun)(char*,int) = Function;
ライブラリ関数を用いる時、(threadなど)やシグナルを使うときに例外ハン ドラを指定する 時によく使う。引数の数が多数あると面倒なことが多いので(型が合わない)引数は 構造体に入れて、関数内にはその構造体へのポインタを渡すようにするとよい。 (threadなどでは、そうする)
関数へのポインタから別の型の関数へのキャストが可能。
サンプルプログラム(function_cast.cc) なるほどーと思ったサンプルプログラム↓
サンプルプログラム(cast.cc)

種々の演算子

  数値演算のための演算子は良いとして。

  演算子(ポインタ、関数関連)
() 関数適用の演算子
評価順序変更演算子
キャスト(型変換)演算子
[] 配列参照の演算子
* ポインタの内容参照の演算子
& 変数のアドレスを得る演算子
-> ポインタを用いた構造体/オブジェクトの変数(メソッド)の参照
? : 条件判定の演算子。条件の真偽により異なった「値」を返す。

  if文は「文」であるからそれ自体値を持たないの に対して、 ? :は全体として値を返す条件分岐である。もちろんif文の代わりとして用いる ことも可能である。(例えば bool? printf("true\n"): printf("false"))
 しかし、複雑な処理をするのに、この演算子は適当ではない。条件分岐であること がわかりにくい。また演算子であることからも、全体の値が要らないときはif文を用いる べきである。

  演算子(bitwise演算)
& Bitwise and
| Bitwise or
^ Bitwise xor
~ Bitwise not

  演算子(論理演算)
&& 論理積 (and)
|| 論理和 (or)
! 否定 (not)

真偽値は0x0が偽(false)であり、その他の値は真である。
ちなみにC/C++には「累乗」の演算子は存在しない(ライブラリのpow関数を 用いるか、自作する)。

  コンマ(,)(カンマ???)
  たとえば、for文において、初期化に入れたい式が複数ある場合、コンマで区切って 式を並べれば良い。普通 ; で区切る文であるが , で延々と区切ってもコンパイルは通り、 正常に動作した。
  for(int i = 1, target = 0x1; i < 20; i++,target <<= 1) ...

  優先順位にきをつけよう。deref_incr.c

  ->演算子の多重定義
とっても特殊な演算子だと思っていたら、多重定義できてしまうという。無条件というわけには いかない。なぜなら->の右には値ではなく「名前」がくるから。そして、この演算子は ポインタを返さねばならないらしい。かなりなぞを秘めている
サンプルプログラム(->を多重定義)
こんなことするのかといわれると、auto_ptrで行なっている。auto_ptrとはポインタをラップする 構造体で、あるスコープで局所変数として確保し、スコープからでた時にポインタがdeleteされる ようにするもの。特にtry文の中で必要。

値渡しと参照渡し

  関数内部から引数を変更すると、はたして、その関数の呼出側で引数の値が変わってしまう のか?Cでは変わない(値渡し)。C++では値渡しと参照渡しの両方を用いることができる。関数の宣 言時に普通に引数を宣言すると値渡しであるが、仮引数名のまえに & をつけると参照渡しにな る。関数内部でこの引数を変更すると呼出側が与えた引数の値も関数呼出後に変更される(つまり 副作用が生じる)。Cで副作用を及ぼすような関数を書くにはポインタを使う。

ポインタを用いて副作用を起こす例
void Set(int *arg) { *arg = 1;}
(呼出側では)
int a; .... Set(&a);

参照渡しにして副作用を起こす例
void Set(int &arg) { arg = 1;}
(呼出側では)
int a; .... Set(a);

  コメント)これって、副作用が起きることを、関数呼出側に意識させるかどうかの問題に なると思います。参照渡しにしても、この関数のプロトタイプ宣言を見て、参照渡しになっている ことを確認するまたは、ドキュメントを読むことで副作用がでることを知って使うといった感じ なのでしょうか?一方、ポインタを用いる側では呼出側もポインタを渡さねばならないと少なくとも 意識してつかいますよね。参照渡しは下手に使うと知らぬ間に副作用が起こっていて、デバッグが 面倒になりそうなのですが、どうでしょうか。
  この参照渡しが有効であるのは演算子の定義の時ぐらいでしょう。
  と思いきや、C++を使い込んでいくと有り難みが分かります。ライブラリは呼び出す ものだったのが、ユーザーが呼び出される側にも回ってきてるからと言えます。どうでしょう。

参照

  参照は必ず初期化しなければならない。参照型メンバ変数を持つ場合は、 コンストラクタの初期化リストに、その変数を初期化するように書けばよい。参照はオブジェクトではない。
  サンプルプログラム(value.cc)

文字列リテラル

  サンプルプログラム(literal.cc)

autoとstatic

  static変数はブロックをでたあとも残る。auto変数はブロックを出ると 消える。
  サンプルプログラム(auto.cc)

引数の評価順序

  第一引数に起こした副作用は第二引数に伝搬した。つまりは、引数は 第一引数から順に評価されるということか?
  サンプルプログラム(increment.cc)

組み込み型のコンストラクタ

  int,doubleなどにもコンストラクタが存在する。
  サンプルプログラム(double.cc)

初期化リスト

  特にconstであるメンバ変数の初期化、参照型であるメンバの初期化、呼び出す コンストラクタの指定をするとき、コンストラクタの仮引数リストの 後に初期化リストを入れることで初期化できる。ここで初期化するメンバ変数は非静的 でなければならない。
class Example {
public:
    Example(int param) : data(param) {}; //初期化リスト
    ~Example(){};
    int data;
};

デフォルトコンストラクタ

  デフォルトコンストラクタ(引数なしまたはすべての引数がオプショナルのコンスト ラクタ)を呼んでインスタンスを生成するとエラーがでた。例えば、RefにコンストラクタRef() が定義されているとする(デフォルトコンストラクタ)。以下のようにインスタンスを宣言する。
  Ref data();
するとそのインスタンスに対する操作で以下のようなエラーがでる。

ref.cc: In function `int main(int, char **)':
ref.cc:28: request for member `Vec' in `test', which is of non-aggregate type `Ref ()()'
ref.cc:29: request for member `hoge' in `test', which is of non-aggregate type `Ref ()()'

  Ref data;
とするとエラーがでなくなった。
  サンプルプログラム(default_constractor.cc)

メンバ関数へのポインタ

->*,.*で参照。::*で宣言する。
  サンプルプログラム(memptr.cc)
  サンプルプログラム (memptr2.cc)

privateメンバを持つ構造体

  structでもprivateメンバを持つことができる。friend関数はこのprivateメンバに アクセスできる。
  サンプルプログラム(private_struct.cc)

constオブジェクト

  関数の引数に構造体が来ると値渡しをするので、スタックか何処かに引数をコピー しなければならない。それでは効率が悪いので、ポインタや参照で引数を渡して欲しい、 しかし、値を変えないというときには値を変えないことをconstで明示的に宣言できる。 これで、構造体メンバをポインタ、参照で直接参照することで効率はあがる。 なおconstと宣言されているものの値を変更している場合は以下のようなエラーが出る。 最初はconst pointerに対する変更、二つ目はconst referenceに対する変更である。
const_pointer.cc: In function `void function(const int *)':
const_pointer.cc:12: assignment of read-only location
const_pointer.cc: In function `void function2(const double &)':
const_pointer.cc:16: assignment of read-only reference `data'
  サンプルプログラム(const_pointer.cc)
constは直後の宣言子を修飾する。
//charが変更不可能。つまり hoge[0][1] = 'c'; はエラー。
const char* hoge = { "mame", "tak", "hoge" };
//配列の要素であるポインタ値が変更不可能 hoge[0] = "hoo"; はエラー
char* const hoge = { "foo", "goo", "zoo" };
//どちらも
const char* const hoge = { "goo" "zoo" "mamewo"};
  const int * const ip; とか。 const.c

volatile指定子

volatile指定された変数に関するコードに対して最適化を積極的に行わないように コンパイラに宣言する。並行処理に良く使用される。(変数を介してのデータの受渡し。)

operator=を多重定義する理由

operator=は自分自身が代入されるのをチェックするために多重定義する。

可変数引数マクロ(Variadic Macro)とその応用の一例

gcc のプリプロセッサ CPP の拡張機能として可変数引数マクロがある.
#define hoge(x, moge...) printf(x, moge)
なんて書くと可変数な部分が moge に束縛されてマクロがかける. ## っていうのがマクロでの文字連結 関数.上の例で x , ## moge ってかいた時に可変数引数部分になにも渡さなかったら ## の前の コンマから後を消して くれる.(see gcc info: Macros with a Variable Number of Arguments.)

エラー処理

便利なマクロ

__LINE__行番号(10進数)
__FILE__ファイル名
__DATE__コンパイル時の日付
__TIME__コンパイル時刻

エラーメッセージ

  perror(const char *s)で直前に起きたエラーのエラーメッセージを表示 できる。引数の文字列に コロン、エラーメッセージが続く。例えば次のようにエラーメッセージ を書くと便利。

  char message[1024];
  sprintf(message,"line %d",__LINE__);
  perror(message);

    参考 例外処理

入出力

書式付き読み込み

  #include <stdio.h>
  int fscanf(File *streamconst char *format,...);

    ファイルストリームから読み込み
  int scanf(const char *format,...);
    標準入力から読み込み
  int sscanf(char *string,const char *format,...);
    第一引数の文字列から読み込み。
  サンプルプログラム(scanhex.cc)

書式付き書き込み

  #include<stdio.h>
  int fprintf(File *stream,const char *format,...);
    ファイルストリームへ書き込み。
  int printf(const char *format,...);
    標準出力へ書き込み。
  int sprintf(char *string,const char *format...);
    第一引数の文字列への書き込み。第一引数(destination)と、第三 引数の要素たち(source)は一致していてもうまくうごいた。
  サンプルプログラム(sprintf.cc)

    蛇足1: printf("%s\n",NULL); の結果はなにか?実験すると(null)という文字列が表 示される。これはgcc,g++ともに同じであった。(他のコンパイラは???)
  サンプルプログラム(nullwrite.cc)

    蛇足2: cout << true << endl; を実行すると味気なく1と表示される。もちろん cout << false << endl; の出力は0
  サンプルプログラム(boolprint.cc)

  コメント)あれ?printf("%s\n",NULL); の結果が不思議に思えるな。僕だけ??NULLは値としては0x0だけど、文字列として出力 すると、特別な存在(null)になるわけだから、 特別扱いされているんだなぁ。と思いきや、falseをprintfで表示すると printf("%s\n",false); (やってみて下さい)なるほど、そういうわけか。(納得)もちろんtrueでこんなことやると (printf("%s\n",true);) Segmentation Faultが起きるでしょう。

書式

  空白文字を書くと、1つ以上の空白を読み 飛ばすことを意味する。
  書式指定子(%...)で変数の方を指定
  その他の文字はマッチしなければ失敗し、データ読み込みを終了する。
   %自身は%でエスケープすることで入れられる。
     e.g.)
       char format[32];
       sprintf(format,"%%0%ds\n",10);
       printf(format,"mamewo");

文字

\0文字列の終端0x00 (NUL)
\abell0x07 (BEL)
\bバックスペース0x08 (BS)
\n改行0x0A (LF)
\r同じ行の行頭へ0x0D (CR)
\tタブ0x09 (HT)
\v垂直タブ0x0B (VT)
スペース0x20 (SPACE)

エスケープシーケンス

エスケープのASCIIコード(0x1b)に続いて色情報、カーソル位置情報を端末に送ることができる。 色や強調などの書式は以下のようにする。
  \x1b[...;...;...m
...のところは強調情報を表す数字を入れる。;で区切って複数指定できる。太字、reverse、 アンダーラインなどなど。 カーソル位置については後日。

  エスケープシーケンスを用いたサンプルプログラム

入力

文字列の入力
  #include<stdio.h>
  int fgets(char *s,int size,FILE *stream);
  ストリーム*streamからsizeより一文字以上少ない文字をsが示すバッファに書き込 む。改行またはEOFを読み込むと読み込みを終了する。改行文字はバッファに書き込まれる。 '\0'がバッファの最後の文字の次に書き込まれる。

バイナリファイルの入力
  #include<stdio.h>
  size_t fread(void *ptr,size_t size,size_t nmemb,FILE *stream);
  データサイズがsize,データの個数はnmemb。streamがさすストリームからnmemb個 のデータをptrがさす場所に格納する。たとえば、intのデータ(たいてい4byte)をファイルから 読み込むときは以下のようにする。
   int buffer[1024];
   FILE *file = fopen("filename","r"); //ファイルを読み込みモードで開く
   fread(buffer, sizeof(int), 1024, file);

  ファイル入出力サンプルプログラム集
    frweof.cc
      fread,fwriteでのファイル入出力。
    fgetseof.cc
      fgetsをもちいた一行単位での入出力。 文字列の入出力をする時に便利。
    noecho.c
      tcgetattr / tcsetattr を用いて端末を設定した例 (参考 Passwordってどうやって得るの?)

記憶領域操作

#include <string.h>
char *memcpy(void* dest,const void* src,size_t len);
  直接コピー
void *memmove(void* dest,const void* src,size_t len);
  バッファを介してコピー
void *alloca(size_t size);
  スタックフレーム上に size分のメモリを確保する。呼出元にもどるときに 確保したメモリ領域は解放される。一時変数の領域確保に使う。Escape するときは コピーするのを忘れずに。

文字列操作

#include <string.h>
char *strncpy(char *dest,const char *src,size_t n);
  srcの先頭n文字を越えない文字をコピー。srcの文字列がnより短い時は destの残りがNULで埋められる。一方、srcの文字列がnより長い(srcの先頭n個にNULが 現れない)ときはn個コピーされてdestはNUL終端しないものになる

多次元配列

  多次元配列はサイズが固定のものは、その配列があるスコープ内では、普通に 扱える。問題は多次元配列を関数の引数として渡すときにどうするか?
  多次元次元配列(e.g. array[i][j])はCでは連続した領域を多次元配列と見て使っている。 配列の添字計算は、オフセットを計算することでおこなっている。
  たとえば、int array[10][20] という配列が宣言されたときarrayはarray[0][0]のアドレスを表す。ここをベースアドレス として、array[2][3]のアドレスは (array + 20*2 + 3)というふうに乗算を使って計算される。 (array[2]は (array + 20*2)なるアドレスを表す) これはコンパイル時点に(つまり静的に)決まらなければならない。
  そこで、関数に多次元配列を渡したいときはポインタの配列を関数に渡してやることに する。ポインタの配列自体は一次元にする。たとえば、上のarrayに対し、 int **pointer_array(またはint *pointer_array[])を用意し、この配列にarray[1],array[2], ...,array[n]を入れてやればよい。(この初期化が必要)こうするとpointer_array[i][j]で array[i][j]を参照できる。 pointer_array[i][j]は、「ポインタ配列であるpointer_arrayのi番目の要素を参照し、 そのアドレスをデータ(ここではint)の配列と見てそのj番目の要素を返す」という意味である。 ポインタ配列の例として、main関数の第二引数である*argv[]がある。この配列には、 コマンドラインからの入力の字句を区切り、各々の引数(コマンド名もふくむ)の文字列(charの 配列)へのポインタを格納している。(ちなみにmain関数の第一引数はint argcで、引数の数 (コマンドを含む)を表している)

  多次元配列のテストプログラム

  C++ではnewというメモリ確保の演算子が追加された。このnewを使うと多次元配列の 領域は次の例のように確保できる。
double (*matrix)[100];
matrix = new double[50][100];
delete [] matrix;

注)new演算子の詳細については知りません。なぜこのように宣言するかも今はよくわかりません。 とりあえず、簡単な実験プログラムだけ載せます。

  new演算子による多次元配列のテストプログラム

C++での拡張点(一部)

継承サンプルプログラム集

親の変数を引き継ぐ (inheritval.cc)
親の関数を引き継ぐ (inheritfunc.cc)
親から引き継いだ関数を上書きする (inheritfunc2.cc)
親と子のどちらの関数を呼ぶか?
    仮想関数 (inheritvirtual.cc)
    仮想関数でない (inheritover.cc)
抽象スーパークラス (abstruct.cc)

更なるキーワード
  静的メンバ関数、静的メンバ変数(static)
  多重継承(!?)

省略可能引数を持つ関数の定義

  省略可能引数をもつ関数の宣言は以下のようにする。
[return_type] [function_name] ([declarations_of_non_optional_arg...],
          [declarations_of_optional_arg]);
[declarations_of_optional_arg] := [type_name] [arg_name] = [default_value],...

分かりにくいので例を示す。
void Foo(int value,char name = NULL);
C++は仮引数の第i引数は、関数適用時の第i引数に束縛する。束縛する引数を名前で指定できな いので省略可能引数は省略不可能な引数の後に来なければならない。また、省略可能引数 が複数ある場合、i番目の引数(これを省略可能とする)を束縛(指定)するには、その引数の i-1個の引数はすべて束縛されなければならない。

関数オブジェクト

  忙しいのでサンプルプログラムのみ。コメントはあとで。
  関数オブジェクトのサンプルプログラム(function_object.cc)

多重定義

  =(代入)演算子も多重定義できるが、これは非静的メンバ関数でなければ ならない。=は継承されない。
  . , .* , ?: , :: は多重定義できない。
サンプルプログラム(subscript.cc)
  []の多重定義
サンプルプログラム (assign.cc)
A a = A("hoge");
は初期化.operator=は呼ばれない.
それ以降 a = b は代入.多重定義した代入演算子でthisのメンバを見ている.
サンプルプログラム(conversion.cc)
  とりあえずここにおく。引数1のコンストラクタは暗黙の変換を行う。
  (おそらくstringを扱うためだろう string x = "takashi")

テンプレート

  引数の型や、返り値の型が異なるが同じ処理をする(parametric polymorphismを 有する)ものは、テンプレート関数を用いると簡単に定義できる。C++には関数テンプ レートとクラステンプレートがある。

関数テンプレート
宣言は次のとおり。
  template <class [type_variables]>
      [return_type] [function_name]([declarations_of_arg]);

  例を示す。
template <class Type>
Type *My_New(int size) { return new Type*[size]; }

呼び出す際に、型変数にint,doubleなどの型を束縛しなければならない。型変数がすべて 引数の宣言部に現れているときは、型チェックにより引数の型に応じた処理が行なわれる。 上のMy_Newは引数の型からは返り値の型が決まらない。このようなときは呼び出し側が次の ようにしてテンプレート関数(My_New)を呼び出す。なお、型のチェックはコンパイラが行ない、 静的に、関数テンプレートの呼出にあう定義を生成する(instantiation)
char *array = My_New<char>(4);

クラステンプレート

  宣言は次のとおり。
クラスの宣言
template <class [type_variables]>
class [class_name]{ ... };

メソッドの定義(クラス宣言の外)
template <class [type_variables]>
[return_type] [class_name]<[type_variables]>::[method_name] ([declarations_of_arg])..

インスタンスの生成は以下のようにする。
[class_name]<[types]>

value template

よくよく考えると複雑に思える。面白いんだけど。テンプレートの型引数の 所には静的に決定できる値を書くことが出来る。この値にたいしてアドレスをとる 操作はしてはいけない。(メモリに割り当てられると言うよりも静的に展開される)
さらにこの値に対して特別化できる模様。本当かい??本当なのかい????↓
サンプルプログラム(value_template.cc)

テンプレートを利用した部分評価 (cf Todd Veldhuizen)

C++は静的に型付けを行うので、型に関わるテンプレートの展開も静的に 行われる。テンプレートの型引数は必ずしも型変数である必要はなく、コンパイル 時に決まる定数であっても良い。この静的に展開される性質を使って部分評価を 行うことができる。struct内で宣言するenumを変数として使って階乗を計算した 例。
サンプルプログラム(static_fac.cc)
prologのような感じで型から型への関数を書くこともできる。例えば、 double,floatの和の型はfloatにしたいが、intの和の型はintにしたいと 言った場合は下のプログラムのようになる。(実際はTはdouble,floatに限らず int以外の全ての型でありうる)
サンプルプログラム(sum.cc)
サンプルプログラム(name.cc)
  何に使えるかは思い付きません↑
サンプルプログラム(variable_inherit.cc)
  え?? Call by value!? Nameじゃダメ??↑
サンプルプログラム(variable_inherit2.cc)
  traits.

テンプレートのリンクに関する問題 (g++)

テンプレートの実体化にはBorlandモデルとCfrontモデルがある。というのは g++のinfoに書いてある。g++はBorlandモデルを採用していて、各々の翻訳単位 内にそこで使用されたテンプレートを実体化し、リンク時に実体が同じ定義(同じテン プレートから同じ型で実体化された定義)を一度だけリンクされるようにする。
+- instance.cc ------+ +- instance2.cc -----+
| template <class T> | | template <class T> |
| print(T a) { .. }  | | print(T a) { ,, }  |
| ..                 | | ..                 |
| foo(){ print('a');}| | goo(){ print('a');}|
+--------------------+ +--------------------+
               |               |
               +- main.cc -----+
               | main() {      |
               |   foo();      |
               |   goo();      |
               | }             |
               +---------------+
printの定義の前にstaticはつけていない。この3つのファイルを分割コンパイルし リンクする。
instance.cc / instance.h
instance2.cc / instance2.h
main.cc
リンク法1
g++ main.o instance.o instance2.o -o main
実行結果
1
1

リンク法2
g++ main.o instance2.o instance.o -o main
実行結果
2
2
staticを付けない場合に同じ名前かつ同じ型で実体化されたテンプレートは 同じものと見なされて後にリンクされた関数はリンカによって捨てられている。
template関数の定義にstaticを付けると解消する。

例外処理

  例外を発生させうる箇所をtryのブロックで囲み、そのブロックのあとに catch文を書く。catch文で具体的な処理を記述する。
   try {
       ....
   }
   catch([type] [param]) {
       ....
   }

  例外を投げるにはthrowを使う。throwでなにかデータを投げてcatchはそのデータ を受ける。なにも投げないこともできるが、なにも投げない例外はとらえられない。(下の サンプルを参照。)
  例外処理のサンプルプログラム

上のプログラムについてちょっとコメント。
これは悪戯心たっぷりで書いています。 gotoでtryブロック内に戻るのは "正しい" プログラムではないと思われます。ただ、 g++ ってこういうプログラムをコンパイルして実行するとこうなるのか、へぇ。ぐらいです。 m(_ _)m で、try ブロックに goto で戻るのは許されているか否かはきが向いたら確認 します。確か catch ブロックに飛ぶ前に try ブロック内の局所変数が解放されるので やってはいけないような気がします。(んー、スタックを goto で入ってきたからといって 戻したりするのかな?) このプログラムでいいたかったのはなにも投げないthrow は捉えられないってことでした。

じゃぁ、(ちょっと規格はおいておいて 汗) 実験してみる。goto で飛び込まれたあとの 局所変数はどうよ?
goto_try_block.cc
戻したものを再び戻す訳もなく、tryブロックの中の局所変数に goto 後再び アクセスするとセグメンテーションフォールトになります。つまりどの処理系でも 書いてはいけないようです。 上のプログラムはたまたま try ブロック内で宣言した 局所変数に触らなかったのでうまくいったのでしょう。

んん、ちょっとまった。普通のブロックでおんなじ現象って起きないかな?
goto_middle_of_block.cc
普通にブロックの真中に飛んでみました。そこでは局所変数を宣言していて、 goto で飛んだ先でそれにアクセスします。普通にうまくいきます。ブロック内の 局所変数はいつ解放されるのかな? ブロックでた後に解放されてるみたいだけど、 中に飛んだらまた使える。 あ、あれ、なんでだろう?

結論: gotoは使わないのが無難。
もっと正確な結論: 注解 C++リファレンスマニュアルより
p113 6.6ジャンプ文の最後の一文によると
"try-block"または"handler"への制御の移動は誤りである。

  例外は投げられたブロックからcatchされられるまでブロックを逆戻りする。 その際に局所変数をデストラクタで開放する。ブロック内でヒープに確保されたものは 例外によって開放されない(ポインタ自身のみが開放)ので、auto_ptr (<memory>に ある)でポインタを包んで開放させる事ができる。
  catchのブロック内でthrow;と書くと今処理している例外を投げる。部分的な 例外処理を書いて、呼出側に残りを行わせることができる。

インライン関数

  マクロのように関数を呼び出しているの部分でその関数の定義内容を 展開する機能。関数呼び出しには、レジスタの待避、スタックpush,popなどがおこるが、 そのオーバーヘッドをなくして、効率をあげることができる。ただし、再帰呼び出しをかける コードはインライン化できない。その関数のアドレスが参照される場合も、関数のコードを 生成しなければならず、インライン化できない。インライン関数にするには関数宣言の前に inlineキーワードをつければ良い。なお、関数の インライン化は最適化の一種であるので、g++では、-O をつけて最適化をしなければ行われない。また、-finline-functions オプションをつけてコンパイルすると、十分単純な関数はインライン化される。インライン化 は関数呼び出しのオーバーヘッドをなくして効率をあげることができるが、その関数を呼び出す 個所が複数あり、関数内部のコード量が多いと、コードサイズを増加させるという欠点がある。

その他の拡張点

コンパイル

プリプロセッサ

置換

  #define [macro_name] [token]
  このようにマクロを定義しておくとmacro_nameをtokenで置き換えます。 定数を定義することができますが、定数定義にはふつうにconstを用いた方が良いです。 マクロはうまく使わないとデバッグではまります。ちなみに文字列リテラル("TAKASHI"など) は置換の対象にはなりません。

ファイルの読み込み

  #include [filename]
  filenameというファイル名のファイルの内容を書いたのと同じ効果を及ぼす。 たいていは、ヘッダファイルを読み込むのに使う。ソースの一部を別のプログラムによって 書き換えうるときなどは、その部分を別ファイルとして#include で読み込むと便利である。にfilenameを指定するには<>で括る方法と""で括る方法 の2つがある。
    <>で括った場合は、コンパイラのシステムディレクトリからファイルを探す。
    ""で括った場合はカレントディレクトリからも探す。
コンパイルオプション-Iで指定したパスにあるファイルは<>で括ってもよい。 ""で括るとカレントパスからの相対パスでファイルを指定することができる。

条件つきコンパイル

  #ifdef [macro_name]
     [code]
  #endif

      macro_nameがマクロとして定義されていれば、#endifまで のコードをコンパイルする。定義されていなければコンパイルしない。マクロは、ソースで定義 するか、コンパイルのオプションとして定義する。
ソースでは
  #define [macro_name]
  (加えて gccでは gcc .... -D[macro_name]とオプションからも定義できる)
条件つきコンパイルを使うことでソースをコメントアウトするのと同様の効果をえることが できる。また、ソースのデバッグのためのコードを条件付きでコンパイルすることで、オプショ ンにより、デバッグ用のコードをコンパイルするかしないかを選択できる。

分割コンパイル

  ソース自体(関数の定義)をインクルードして別ファイルで定義した関数を参照す るという方法もある。しかし、これではすべてのファイルがコンパイル対象になってしまい、 デバッグも面倒である。そんなときに分割コンパイルが便利である。それぞれのソースを リンクを行なわずにコンパイルしオブジェクトファイルを生成する(拡張子 .o)それらを リンカにより結合してプログラムを作る。ここで、問題となるのが他のファイルで定義さ れている関数への参照や、大域変数への参照である。これらの問題を解決するためにヘッダ ファイルと実装のファイルを分けると良い。以下で、分割コンパイルについて説明する。

ヘッダファイル

  関数の定義のファイル(拡張子 cc/cpp etc..)に対して、それと同じprefixを持つ ヘッダファイルを用意する。(たとえばmamewo.ccに対しmamewo.h) そしてヘッダファイルには クラスの宣言、定数の宣言、型の定義、関数のプロトタイプ宣言、大域変数に対する extern宣言をおくと良い。関数のプロトタイプ宣言は
    [return_type] [function_name]([declarations_of_arg]);
のようなもので、関数の存在を表している。ヘッダファイルは条件つきコンパイルを用いて 2重インクルードを避ける。
    #ifndef [macro_name]
    #define [macro_name]
    .....
    #endif

  他のファイルで定義されている関数を参照する場合は、その関数の存在を示すヘッダ ファイルをインクルードする。こうすることで、他のファイルで定義されている関数への参照 の問題は(一応)解決する。(リンク時にこの関数が定義されていないときに参照の問題がまたで てくる)大域変数はmain関数を定義しているファイルで実体を宣言するのがよい。(対応する ヘッダファイルには大域変数に対するextern宣言をおく)ヘッダファイルで実体を宣言してし まうと、そのヘッダファイルが2つ以上のの異なるファイルからインクルードされていたとき に、同じ名前の変数が二度宣言されていることになりコンパイルエラーが起こる。
  ちなみに、関数のプロトタイプ宣言は1つのファイル内での関数の参照の解決にも 役立つ。Cは呼び出される関数は呼び出す関数の前に定義されていなければならないが、 (されていない場合はextern宣言でも解決できる)プロトタイプ宣言を用いて存在を示しておく ことで、定義する順番を気にしないで関数を定義することができるし、相互に参照し合う関数 の定義も楽になる。

オブジェクトファイルの生成

  コンパイルオプションの項にもあるが、-cオプ ションをつけることで(gcc,g++は)オブジェクトファイルを生成できる。ただ、インクルード されるヘッダファイルの場所を解決しなければならないので-Iオプションでヘッダファイルの パスをコンパイラに知らせなければならない。

サンプル

  makeと分割コンパイルのサンプルです。g++が必要です。ちょっとかえるとgccでも コンパイル可能です。それほど簡単なプログラムです。ただ、引数をシフトするだけ。
  サンプル(local.tar.gz 8,173 byte)

リンク

  オブジェクトファイルをあわせて1つの実行ファイルを作るのがリンクである。 オブジェクトファイルを作るときには大域変数の存在、他の関数の存在などはヘッダファイルで 存在することは教えられているが、実物がちゃんとあるとは限らない。リンカはこのヘッダファイ ルが教える通りに実体があるかを見る。もちろん、使用されない関数は存在があってもなくても 関係ない。
  リンクについて詳細を知るわけではないので、ちょっとしたことだけ書いておく。

ldのオプション

ldはリンカである。おそらくコンパイルオプション(gcc,g++に与えるオプション)からリンカの 動きを制御できるが、先日直接リンカを使ってしまったので、ここに書いておく。
-a静的モード
-r再配置可能オブジェクトを結合し再配置可能オブジェクトを生成
-b動的モード
-e [epsym]エントリポイントのアドレス
-l [library_name]ライブラリの指定
-L[library_path]ライブラリのパス指定
(環境変数LD_LIBRARY_PATHも参照される)
  静的モードでは未解決の参照(実体がないもの)があるとエラーを出す。静的モードでは、 そこでリンクが完結するからである。動的リンクは実行時にリンクされるので、オブジェクト ファイル内でやはり未解決の参照があるかも知れない。

リンクのキーワード

ライブラリ

  stdioもstdlibもすべて実体をもつ。つまり誰かがコードを書かねばならない。 その誰かが書いてくれたコードをオブジェクトにして集めたのがライブラリである。
UNIX系のOSでは、/usr/lib , /usr/local/lib など、libという名前のディレクトリに ライブラリをおくことが多い。名前のつけ方はlib[library_name].[extension] (extensionは .a .so) 標準ライブラリはDefaultでリンクされる。ソケット通信を行うとか、 スレッドを用いるときにはコンパイルオプションでライブラリを指定してリンクしなければな らない。どのライブラリをリンクさせるかはmanを参照すると良い。(compileの仕方のところに、 -l[library_name]のように書いてある。)

gcc(g++)の主なコンパイルオプション

-Sアセンブリ言語に変換
(拡張子はs)
-Eプリプロセッサの処理までを行なう
-Mプリプロセッサによってmakeで使用可能な依存関係の 記述を出力する
-dM(-Eと共に使用し)定義されているマクロとその値を表示する
(プリプロセッサの処理のみ行なう) cppのオプション
-D[macro_name]=[value]マクロの値を指定
-o [file_name]結果の出力ファイル名の指定
-cオブジェクトファイルを生成
(リンクを行なわない 拡張子はo)
-I[path]includeファイルのパス指定
-L[library_path]ライブラリのパス指定
-v標準エラー出力に、現在実行されているコマンドを表示する。
-ftemplate-depth-NNテンプレートのinstanciateの深さを設定する
-finline-functions十分単純な関数をinline化する
-frepoテンプレートの実体化がどこで行われたかを 示した .rpoファイルを生成する。
-fno-implicit-templates暗黙のテンプレートの実体化を 行わない。プログラム全体で一箇所だけでテンプレートを明示的に実体化することで、 オブジェクトファイルサイズの和が減少し、リンクが速くなる。
-O0最適化を行わない
-O1最適化
-O2もっと最適化
-O3もっともっと最適化
-Osコードサイズに関する最適化
-pggprofで使用できるプロファイル情報を書き込むコードを生成する
-pedanticANSIの規格にしたがっていないプログラムに対して警告をする
プロファイルのとりかた
  1. -pgをつけて対象のプログラムをコンパイルする
  2. 対象のプログラムを実行する。終了するとデフォルトで gmon.out というファイルが作成される
  3. gprof [プログラム名] を実行するとプロファイル結果が出力される

標準ライブラリや、その他のライブラリを使ってみよう

parsedate, ctime

時間を扱う関数。 parsedate は libinn によって提供されている。日付を表す 文字列を読み込んで、 time_t 型の値 (世界標準時で1970/1/1 00:00:00 からの経過秒数) に変換する。
ctime は逆に time_t 型の値からコンピュータのタイムゾーン設定をみて、 そのタイムゾーンでの日付を表す文字列に変換する。

japanese_time.c
  つまり、 parsedate して ctime するとGMTで日付を表している文字列を日本での 時刻を表す文字列に変換出来る。

strptime

libinn を使わずとも日付を表す文字列を tm 構造体に変換出来るのかと調べてみたところ、 strptime という関数が あった。でも、これを使うときは日付を表す文字列の書式を知っておく必要がある。 getdate はこの書式をファイルから 取得する関数である。

strptime_test.c
  タイムゾーン (%Z) は GNU の拡張。 GMT から JST への変換はどうするのかなぁ。このコードみたいに +0900 するのだろうか?
余談
  時間関係の関数でポインタを返すもの (asctime, gmtime ...) はこと如く global 変数か static な変数へのポインタを返しているらしく、 複数のスレッドを使うプログラムでは安全でない。(他のスレッドに書き換えられるかもしれない) ので、引数に出力用のバッファを渡すバージョン が用意されている。関数名の最後が _r になっているのがそれ。

メモ

=はデフォルトで定義されるものと The Annotated C++ Reference Manualにはあるが、 VC++ではそうではないようだ
動的に長さが決まる配列はGNU拡張。ANSI Cにはない。
サンプルプログラム (dynamic_array.cc)
そこでvalue template。
サンプルプログラム (template_array.cc)
そして、The Annotated C++ Reference Manualの重要な部分は巻末のANSI/ISOの 決議であるような気がしてきた。
Posix 正規表現ライブラリ
  regcomp, regexec, regerror, regfree
小技集
gcc 2.94.3 で
定義されるマクロを見る
  cpp -dM md5.c
-dCHAR オプションでいろいろ情報が得られる
include されるファイル名を見る
  cpp -H md5.c > /dev/null
しかも、includeの段数に応じてインデントつきで表示される。
ヒントメモ
モジュール間の依存関係解消にはより小さい単位である変数と関数の依存関係とか、 変数の初期化の間での依存関係をみてインターリーブすべきか?? 互いに初期化コードが 見えてる必要あるかも?

Linux関係
pause_test.c signal番号は "bits/signams.h" にある。
noecho.c
simple_wget.c 読むべきマニュアル、ヘッダファイルが多い。。。。
sslwget.c SSL (OpenSSL) をつかってHTTPアクセス (443番ポート)
realpath.c 欲しい時ありますよね?? (相対パスから絶対パスへの変換)
popen.c プロセスとつながるパイプを FILE* に変換して 返してくれるけど、読むのみか書くのみかのどちらかのみ。。。。。
メモ。 double-fork ゾンビを残さない。端末からの切り離し
if (fork () == 0) {
  if(fork() == 0) {
    do_something()
  }
  exit(0);
}

C言語的サンプル
sizeofは引数で与えられた式の型のみに依存するわけではない。sizeof_is_not_functional.c
sizeof_literal.c

メモリ管理
  Dmalloc - Debug Malloc Home Page
  A garbage collector for C and C++
    Boehm GC

ライブラリで遊ぶ
  iconv_test.c
   iconvを使って文字コード変換
  readline.c
   readline は補間のカスタマイズもできるらしいぞ。詳しくは info に 書いてある。readline によって返されたメモリ領域はちゃんと free しよう。
参考
  The GNU Readline Library

参考文献

エイチアイ著
「実用UNIX C/C++言語ハンドブック」
ナツメ社
  注) 著者エイチアイは会社の名前で執筆をされたのは
    川端一生さん
    真田良蔵さん だそうです。

「詳細C++ ANSI C++完全理解」
SOFT BANK 詳説C++―ANSI C++完全理解 (大城 正典)
STL標準講座 (Herbert Schildt)
gccのinfo (英語)
  基本.

今後のターゲット

いろいろ
  構造体を引数として渡す
Stream Class
関数オブジェクト
静的メンバ変数、静的メンバ関数
constメソッド
export
class宣言部におくメソッドの定義(inline化される)
コピーコンストラクタ

リンク

Language C FAQ
GCC Home Page - GNU Project - Free Software Foundation (FSF)
  GCCのホームページ。
Using and Porting GNU CC
Standard C++ Library v3
  libstdc++-doxygen-3.0.tar.gzがC++のライブラリのマニュアル(HTML)
  ファイル数が多いのでファイルリストをブラウザで見ないほうがよいです。 index.htmlにブックマークして使うのがよいかと。なんてflattenな構造なんやろ??
SGI - Services & Support: Standard Template Library Programmer's Guide
  Silicon Graphics社のSTLについて書いたページ。マニュアルもあり。
cplusplus resources - The C++ resources network -
Todd Veldhuizen

おまけ
C++プログラミングのためのEmacsマクロ(mycextend.el)
  (C++モードで適当なキーにバインドして使う)
  開発中。僕が何かソースを読みたい時に開発はすすむかもしれません。
  templateの分割コンパイルに関する論文。部分評価としてのテンプレートなど。
Programs (このサイトのプログラム一覧)
Ocamlで書かれたC のパーザーのプロトタイプ A 3D animation of Linux source code development
  とそれを使ってみたコード c_parser_driver-2004-01-01.tar.gz


作者: 増山隆 address
To Home
Valid HTML 4.0!