C言語/構造体

出典: フリー教科書『ウィキブックス(Wikibooks)』

構造体の基本[編集]

構造体(こうぞうたい; structure)は、構造体は、記憶域が順番に割り当てられたメンバーの列からなる型です [1]。 複合的なデータ構造という意味では配列と似ていますが、配列の要素はすべて同じ型で添字によってアクセスするのに対し、構造体のメンバーは任意の型を取ることが出来、メンバー名によるアクセスするところが異なります。 また、構造体の配列を作ることも出来ます。

たとえば、商品名と価格の2つ組を扱いたいときには、char * 商品名int 価格 からなるデータ構造が考えられます。

商品1
商品名
牛乳
価格
200
商品2
商品名
オレンジジュース
価格
150

構造体の定義[編集]

上述の商品名と価格の2つ組の例をC言語のコードでは、

構造体の定義
struct ProductEntry {
  char *name; // 商品名
  int price;  // 価格
};

もように表すことが出来ます。

また、上記の例の ProductEntry のように構造体に付ける名前は、構造体タグ( structure tags )と呼ばれます[2]

構造体は1個以上のメンバー( member )を持ちます。上の struct ProductEntry の例では nameprice がメンバーです。

構造体変数[編集]

構造体を定義しても、構造体を型として持つの変数 (以下、構造体変数と呼ぶ)は作られません。 構造体を利用するには、構造体の構造体変数( structure variable )を宣言します。

構造体変数の宣言と初期化
#include <stdio.h>

struct ProductEntry {
  char *name;
  int price;
};

void print_product_entry(const struct ProductEntry product) {
  printf("商品名 : %s, 価格 : %d 円\n", product.name, product.price);
}

int main(void) {
  const struct ProductEntry milk = {"牛乳", 200},
                    orange_juice = {"オレンジジュース", 150};
  print_product_entry(milk);
  print_product_entry(orange_juice);
}
実行結果
商品名 : 牛乳, 価格 : 200 円
商品名 : オレンジジュース, 価格 : 150 円

構造体変数を宣言するときは、上述の struct ProductEntry milk; のように宣言します。

また、構造体変数の要素に代入したい場合など、構造体変数を参照したい場合には、「.」(ドット演算子)を用いて、milk.name のように記述します[3]

形式
構造体名 . 構造体変数名

になります[4]

構造体変数の初期化[編集]

構造体変数は、宣言と同時に、値のリストで初期化することができます。

形式
struct タグ名 変数名 = {値のリスト};

値のリストは「,」(コンマ)で区切ったリストです。

構造体変数の初期化
int main(void) {
  struct {
    int i;
    double d;
    char c;
    const char *str;
  } my_struct = {1234, 3.14, 'a', "Hello, World!"};
}
上の例では、my_struct.i を1234、my_struct.d を3.14、my_struct.c を'a'、my_struct.str を"Hello, World!"で初期化しています。
メンバーを指定した構造体の初期化
int main(void) {
  struct {
    int i;
    double d;
    char c;
    const char *str;
  } my_struct = {.str = "Hello, World!", .c = 'a', .i = 1234, .d = 3.14};
}
C99 の機能を使用すると、このように構造体メンバーを任意の順序で初期化することができます。
複合リテラルを使った例
int main(void) {
  struct MyStruct {
    int i;
    double d;
    char c;
    const char *str;
  };
  struct MyStruct my_struct;
  my_struct = (struct MyStruct){.str = "Hello, World!", .c = 'a', .i = 1234, .d = 3.14};
}

複合リテラルを使うと、初期化のコンテキストでなくても(キャストに似た構文で)リテラルによる一括代入が出来ます。 ただ、複合リテラルはスコープの中で一度しか生成されないので、ループの内側で使うと期待とは違った値を得る結果になります。

コード例
(省略)

構造体へのアクセス[編集]

構造体のメンバーにアクセスするには、 「.(ドット)演算子」を用いて、次のように記述します。

形式
構造体の変数名 . メンバー名
構造体の読み取り
#include <stdio.h>

int main(void) {
  struct {
    int i;
    double d;
    char c;
    char str[32 + 1];
  } my_struct;

  printf("整数を入力してください。:");
  if (scanf("%d", &my_struct.i) == EOF) { // 整数入力を my_structure.i に格納します。
    printf("End of File に達しました。\n"); 
    return 1;
  }
  printf("浮動小数点数を入力してください。:");
  if (scanf("%lf", &my_struct.d) == EOF) { // 浮動小数点数入力を my_structure.d に格納します。
    printf("End of File に達しました。\n"); 
    return 1;
  }
  printf("文字(半角1文字)を入力してください。:");
  if (scanf("%c", &my_struct.c) == EOF) { // 文字入力を my_structure.c に格納します。
    printf("End of File に達しました。\n"); 
    return 1;
  }
  printf("文字列(半角31文字、全角15文字まで)を入力してください。:");
  if (scanf("%32s", my_struct.str)) { // 文字列入力を my_structure.str に格納します。
    printf("End of File に達しました。\n"); 
    return 1;
  }
  printf("my_structのメンバーの値は、%d %f %c %sです。\n", my_struct.i,
         my_struct.d, my_struct.c, my_struct.str);
}
上の例では、ユーザーからの4つの入力を構造体の4つのメンバーに格納し、その4つのメンバーの値を表示しています。
※ 過去の編集で、9行目が const char * str;となっていましたが、未初期化のポインター変数による書き込み参照を生じていました。

構造体の代入[編集]

配列とは異なり、構造体は一度に代入することができます。構造体を代入する時は、構造体の変数名のみを用います。 構造体の代入のことを「コピー」という場合もあります。

形式
構造体の変数名 = 構造体の変数名;

これにより、右の構造体の全てのメンバーが左の構造体の全てのメンバーに代入されますが、以下の点に注意する必要があります。

漏洩への配慮
代入の右辺の構造体に隙間が含まれていることがある。その隙間に過去に使われた時の値があり、それらも代入されます。
そのため、代入の左辺の構造体が外部からアクセス可能な場合、情報漏洩に繋がるリスクがある[5]
型の一致
代入の右辺の構造体と代入の左辺の構造体は、構造体の型が一致していなければいけません。
浅いコピー(シャローコピー)
ここでいう「構造体の代入」は浅いコピー(シャローコピー)です。そのため、メンバーにポインターが含まれている場合そのアドレスだけがコピーされ、その指している先は代入の右辺と同一になります。これによって、片方の構造体のポインターが指している先を変更すると、もう片方の構造体のポインター先の値まで変わってしまう。
構造体の代入
#include <stdint.h>
#include <stdio.h>

int main(void) {
  typedef struct {
    const char *str;
    double d;
    int32_t i;
    char c;
  } __attribute__((__packed__)) my_struct_t;

  my_struct_t my_struct_1 = {"Hello, World!", 3.14, 1234, 'a'}, my_struct_2;

  my_struct_2 = my_struct_1;
  printf("代入の左辺のメンバーの値は以下のとおりです:\n");
  printf("\n");
  printf("  my_struct_2.str: \"%s\"\n", my_struct_2.str);
  printf("  my_struct_2.d  : %f\n", my_struct_2.d);
  printf("  my_struct_2.i  : %d\n", my_struct_2.i);
  printf("  my_struct_2.c  : '%c'\n", my_struct_2.c);
  printf("\n");
  printf("また、構造体 my_struct_t のサイズは %zu バイトです。\n",
         sizeof(my_struct_t));
}
実行結果
代入の左辺のメンバーの値は以下のとおりです:

  my_struct_2.str: "Hello, World!"
  my_struct_2.d  : 3.140000
  my_struct_2.i  : 1234
  my_struct_2.c  : 'a'

また、構造体 my_struct_t のサイズは zu バイトです。

一方、メンバーに構造体が無い場合は、コピー元とコピー先の構造体はそれぞれ別々に保存されます。

//例 構造体へのポインター
#include <stdio.h>
#include <string.h>

int main(void) {
  struct {
    int i;
    double d;
    char c;
    char str[32];
  } my_struct1, my_struct2;

  my_struct1.i = 1234;
  my_struct1.d = 3.14;
  my_struct1.c = 'a';
  strcpy(my_struct1.str, "Hello, World!");

  printf("my_struct1のメンバーの値は、%d %f %c %sです。\n", my_struct1.i,
         my_struct1.d, my_struct1.c, my_struct1.str); 

  printf("構造体を一括コピー中\n"); 
         
  my_struct2 = my_struct1;

  printf("コピーされたmy_struct2のメンバーの値は、%d %f %c %sです。\n", my_struct2.i,
         my_struct2.d, my_struct2.c, my_struct2.str); 

  printf("my_struct2.iだけ変更→555 \n"); 
  my_struct2.i=555;
     
  printf("my_struct1のメンバーの値は、%d %f %c %sです。\n", my_struct1.i,
         my_struct1.d, my_struct1.c, my_struct1.str);
         
  printf("コピーされたmy_struct2のメンバーの値は、%d %f %c %sです。\n", my_struct2.i,
         my_struct2.d, my_struct2.c, my_struct2.str); 

}
実行結果
my_struct1のメンバーの値は、1234 3.140000 a Hello, World!です。
構造体を一括コピー中
コピーされたmy_struct2のメンバーの値は、1234 3.140000 a Hello, World!です。
my_struct2.iだけ変更→555
my_struct1のメンバーの値は、1234 3.140000 a Hello, World!です。
コピーされたmy_struct2のメンバーの値は、555 3.140000 a Hello, World!です。

要するに、構造体のコピーといえども、ポインタの指し示す先を書き換える権限はない、というだけのことです。よって、メンバにポインタを含まないなら、個別に書き換えることができます。

参考
大きなサイズの構造体はコピーしないほうがよい場合も

なお、大きなサイズの構造体をあつかう場合、構造体全体をコピーするため時間がかかってしまう。 その場合の対策として、構造体をコピーせずに(引数として)参照できるようにすれば良いので、そのためにはポインタを活用する必要がある。 詳しくは、構造体のポインタに関する節で説明する。

大きなサイズの構造体でないなら、特に気にせずにコピーして構わない。実際、上述の構造体コピーのコードは、正常にコンパイルできて動作する。

__attribute__((__packed__))

構造体のメンバーは、プログラムテキストで宣言された順序でメモリー上に配置されます。 構造体のメンバーがメモリー上で適切にアライメントされるように、構造体にパディング(詰め物)を加えることがあります。 多くのアーキテクチャでは、構造体パディングをすることで構造体のメンバーへのアクセスが高速化しますし、アライメントに不整合があるとバスエラーを生じてプログラムが中断することすらあります。 __attribute__((__packed__)) はGCC の拡張機能で、パディングを挿入しないように指示し構造体メンバーの位置をずらすことを可能にします。 本節では、構造体パッディング(に残った値)が情報漏洩につながると論じており、__attribute__((__packed__)) を用いてパッディングの削減を図っています。

単体の構造体同士ではこの手法も有効ですが、構造体の配列ではアライメントの要請からパディングを生じるので __attribute__((__packed__)) も万能ではなく、構造体同士の代入ではなく要素間の代入をする関数を用意しそれを使うなどの根本的な解決方法が望まれます。

そもそも上記の例では、my_struct_1 も my_struct_2 もスタック上のインスタンスなので驚異の質は同じで脆弱性をうまく表現できていません。


typedefによるコードの短縮[編集]

typedefを構造体に用いて、コードを短縮することができます。

typedefを用いない構造体の宣言
struct my_struct {
	int i;
	double d;
	char c;
    char str[32 + 1];
};

int main(void) {
	struct my_struct my_var; // structキーワードが必要
}

上の例は typedef を用いて、下の例のように記述することができます。

typedefを用いた構造体の宣言
typedef struct {
	int i;
	double d;
	char c;
    char str[32 + 1];
} my_type;

int main(void) {
	my_type my_var; // structキーワードは不要
}

おそらく、いちばん有名な typedef された構造体は、 <stdio.h> で定義されている FILE でしょう。

構造体の応用[編集]

構造体を引数に持つ関数[編集]

構造体を関数の引数にしたい場合、下記のように行います。

構造体を関数の引数にする
#include <stdio.h>

typedef struct {
  char* name;
  int price;
} product_entry;

void print_product_entry(const product_entry product) {
  printf("商品名 : %s , 価格 : %d 円\n", product.name, product.price);
}

int main(void) {
  // 構造体の設定
  product_entry milk = {"牛乳", 200},
        orange_juice = {"オレンジジュース", 150};

  // 関数のテスト
  print_product_entry(milk);
  print_product_entry(orange_juice);
  print_product_entry((product_entry){"トマトジュース", 130});
  print_product_entry((product_entry){"グレープジュース", 180});
}

解説[編集]

  • 構造体の定義は、構造体変数(や仮引数)を使うより前に行います。
  • typedef で定義した型 product_entry を使うときには、structキーワードは必要ありません。
  • 20, 21行目は、C99の新機能 複合リテラル( Compound Literal )を使った例です[6]
Visual Studio で、デバッグ セッションの終了時にコンソールが閉じてしまう場合

Visual Studio で、デバッグ セッションの終了時にコンソールが閉じてしまう場合は、

[ツール] -> [オプション] -> [デバッグ] -> [デバッグの停止時に自動的にコンソールを閉じる]

を無効にします。


構造体の配列[編集]

上の例では商品が2個しかなかったから、都度構造体変数を宣言すればよかった。しかし、商品が100個になったり、数が不定になったりしたらどうだろうか?個々の変数に割り当てる方法では厳しくなってくる。 そこで、構造体の配列を宣言し使用することでその問題に対処する。

構造体の配列を作る
#include <stdio.h>

typedef struct {
  char* name;
  int price;
} product_entry;

int main(void) {
  const product_entry products[] = {{"牛乳", 200},
                                    {"オレンジジュース", 150},
                                    {"トマトジュース", 180},
                                    {"りんごジュース", 220}};

  for (size_t i = 0; i < sizeof(products) / sizeof(products[0]); i++) {
    printf("商品名: %s, 価格: %d円\n", products[i].name, products[i].price);
  }
}
実行結果
商品名: 牛乳, 価格: 200円
商品名: オレンジジュース, 価格: 150円
商品名: トマトジュース, 価格: 180円
商品名: りんごジュース, 価格: 220円

書式[編集]

構造体の配列を宣言する時は、変数名の後に「[要素数]」をつけます。

形式
struct タグ名 配列の変数名 [ 要素数 ];

構造体の配列のメンバーにアクセスする時は、変数名の後に[添字]をつけます。

形式
配列の変数名 [ 添字 ] . メンバー名

[]演算子と.演算子は、共に同じ優先順位の演算子で、左から右へと評価されるため (左結合)、次の式と等価です。

( 配列の変数名 [ 添字 ] ) . メンバー名
構造体の配列
#include <stdio.h>

#define N_ARRAY(ary) (sizeof(ary) / sizeof(ary[0]))

int main(void) {
  struct {
    int i;
    double d;
    char c;
    char* str;
  } array[] = {
      {12, 1.2, 'a', "abc"},
      {34, 3.4, 'b', "def"},
      {56, 5.6, 'c', "ghi"},
      {78, 7.8, 'd', "jkl"},
      {.str = NULL}, // 終端
  };

  for (int i = 0; array[i].str != NULL && i < N_ARRAY(array); i++) {
    printf("struct_array[%i]のメンバーの値は、"
           "{ .i = %d, .d = %f, .c = %c, .str = %s }です。\n",
           i, array[i].i, array[i].d, array[i].c, array[i].str);
  }
}

上の例では構造体の配列に初期値を与え、そのメンバーの値を表示しています。 メンバー .str にNULLがセットされた要素が終端を示します。 この例では、配列の大きさを N_ARRAY() で得ることが出来ますが、関数呼び出しの引数に渡された時には呼ばれた関数では長さがわからないので終端の約束事を決める必要があります。 これは、C言語の配列プログラムを扱うプログラムでは広く使われる手法で、Hello World でも。

構造体の配列
    printf("Hello World!\n");

の "Hello World!\n" も '\0' で終端された文字型の配列を引数にしています。

構造体を関数の引数として渡す[編集]

構造体を関数の引数として渡す例
#include <stdio.h>

#define N_ARRAY(ary) (sizeof(ary) / sizeof(ary[0]))

typedef struct {
  char *name;
  int price;
} product_entry;

void func1(const product_entry products) {
  printf("商品名: %s, 価格: %d円\n", products.name, products.price);
}

int main(void) {
  const product_entry products[] = {{"牛乳", 200},
                                    {"オレンジジュース", 150},
                                    {"トマトジュース", 180},
                                    {"りんごジュース", 220},
                                    {.name = NULL}};
  func1(products[2]);
}
実行結果
商品名: トマトジュース, 価格: 180円

構造体へのポインターを関数の引数として渡す[編集]

構造体へのポインターを関数の引数として渡す例
#include <stdio.h>

#define N_ARRAY(ary) (sizeof(ary) / sizeof(ary[0]))

typedef struct {
  char *name;
  int price;
} product_entry;

void func1(const product_entry *products) {
  printf("商品名: %s, 価格: %d円\n", productsー>name, productsー>price);
}

int main(void) {
  const product_entry products[] = {{"牛乳", 200},
                                    {"オレンジジュース", 150},
                                    {"トマトジュース", 180},
                                    {"りんごジュース", 220},
                                    {.name = NULL}};

  func1(products + 2);
}
実行結果
商品名: トマトジュース, 価格: 180円

構造体のネスト[編集]

構造体のメンバーに、構造体を指定することができます。 本項ではこれを「構造体のネスト」と呼ぶことにします。

ネストされた構造体にアクセスする時は、ネストの最も外側から内側に向かって参照しなければなりません。

[1]
#include <stdio.h>

struct Inner {
  int i;
  double d;
  char c;
};

struct Outer {
  struct Inner s;
  int i;
  double d;
  char c;
};

int main(void) {
  struct Outer my_struct = {
      .i = 1234, .d = 1.23, .c = 'a', .s = {.i = 5678, .d = 5.67, .c = 'b'}};

  printf("%d %f %c\n", my_struct.i, my_struct.d, my_struct.c);
  printf("%d %f %c\n", my_struct.s.i, my_struct.s.d, my_struct.s.c);
}
実行結果
1234 1.230000 a 
5678 5.670000 b

上の例では、my_structのメンバーであるsが「構造体のネスト」です。

構造体変数を宣言する際、宣言する構造体の型は、一番外側に相当する構造体になります。なぜなら一番外側の構造体は、内側の構造体についての情報も含んでいるので、よって外側の構造体の変数さえ宣言すれば連鎖的に内側の構造体についても宣言できるからです。

また、ネストの場合のメンバ変数へのアクセス方法の書式は

外側の構造体変数.外側構造体内で用いられている構造体変数.内側構造体の目的変数

です。

構造体配列と構造体配列へのポインター[編集]

※ 別の節で、構造体のポインターに関する節があります。初学者のかたは、そちらを先にお読みください。

商品リストの話に戻ります。 一つの店舗だけでなく複数の店舗の(可変長の)商品リストを扱う場合はどうすればよいでしょう? 1つのやり方として十分大きな配列で確保する方法がありますが、自由領域に長大な(疎な)オブジェクトを置くことになりメモリー(より厳密にいうとスタック領域)を使い果たしてしまう可能性があります。 そこで、複合リテラルで構造体配列を用意することにします。

構造体配列と構造体配列へのポインター
#include <stdio.h>

typedef struct {
  char *name;
  int price;
} product_entry;

typedef struct {
  char *name;
  product_entry *products;
} store;

int main(void) {
  store stores[] = {{"ウィキペディア店",    (product_entry[]){{"牛乳", 200}, {NULL,0}}},
                    {"ウィクショナリー店",  (product_entry[]){{"牛乳", 250}, {NULL,0}}},
                    {"ウィキブックス店",    (product_entry[]){{"牛乳", 170}, {"オレンジジュース", 190}, {NULL,0}}},
                    {"ウィキソース店",      (product_entry[]){{"牛乳", 210}, {"青汁", 540}, {NULL,0}}},
                    {"ウィキクォート店",    (product_entry[]){{"牛乳", 190}, {NULL,0}}},
                    {NULL}};

  for (int i = 0; stores[i].name != NULL; i++) {
    const store *st = stores + i;
    printf("名前: %s, 商品:\n", st->name);
    for (int j = 0; st->products[j].name != NULL; j++) {
      const product_entry *pr = st->products + j;
      printf("  名前: %s, 値段: %d 円", pr->name, pr->price);
    }
    printf("\n");
  }
}

複合リテラルで構造体の配列を作り、特定のメンバーを(文字列の '\0'のように)終端の意味に使い、長さの違う配列を扱っています。

二重配列の構造体変数[編集]

構造体配列は、二重配列であっても良い。つまり、構造体変数には、二重配列を宣言する事も可能です。

よって構造体のネストを使わなくても、下記コードのように二重配列でも代用できます。

コード例
#include <stdio.h>

struct result {
  char* name;
  int score_japanese;
  int score_mathematics;
};

int main(void) {
  struct result student[3][2]; // 構造体配列の宣言   [組][組内番号]

  student[0][0] = (struct result){"山田123", 80, 70};
  student[0][1] = (struct result){"佐藤", 60, 90};
  student[1][0] = (struct result){"安部", 78, 65};
  student[1][1] = (struct result){"小泉", 50, 100};

  for (int klasse = 0; klasse <= 1; klasse++) {
    for (int number = 0; number < 2; number++) {
      struct result *p = &student[klasse][number];
      printf("名前: %s, 国語: %d点, 数学: %d点\n", 
             p->name,
             p->score_japanese,
             p->score_mathematics);
    }
  }
}
実行結果
名前: 山田123, 国語: 80点, 数学: 70点
名前: 佐藤, 国語: 60点, 数学: 90点
名前: 安部, 国語: 78点, 数学: 65点
名前: 小泉, 国語: 50点, 数学: 100点

構造体とポインター[編集]

構造体へのポインター[編集]

構造体へのポインターを宣言する時は、変数名の前に「*」をつけます。

形式
struct タグ名 * ポインターの変数名 ;

ポインターの指す構造体のメンバーにアクセスする時は、「->」(アロー演算子)を用いて次のように記述できます。

形式
ポインターの変数名 -> メンバー名

これは、次と同じ意味です。

形式
( * ポインターの変数名 ) . メンバー名
「.」の方が優先順位が高いため「()」で囲む必要があることに注意してください。

またでは、下記コードで実験してみましょう。

構造体へのポインター
#include <stdio.h>
#include <string.h>

int main(void) {
  struct {
    int i;
    double d;
    char c;
    char str[32];
  } my_struct, *pmy_struct;

  pmy_struct = &my_struct;

  pmy_struct->i = 1234;
  pmy_struct->d = 3.14;
  pmy_struct->c = 'a';
  strcpy(pmy_struct->str, "Hello, World!");

  printf("my_structのメンバーの値は、%d %f %c %sです。\n", pmy_struct->i,
         pmy_struct->d, pmy_struct->c, pmy_struct->str);
}
実行結果
my_structのメンバーの値は、1234 3.140000 a Hello, World!です。

上の例では、構造体へのポインターを介して構造体のメンバーに値を代入したり、その値を参照したりしています。

なお、strcpyは、文字列をコピーするための関数。strcpyを使うには、ヘッダー <string.h> のインクルードが必要です。

また、文字列の宣言は、(上記の char str[32]; のように)文字の配列として宣言します。

Cでは、配列同士は代入できず、それぞれの要素ごとに代入を行います。 加えて、Cの文字列は '\0' で終端するという約束事があり、"ABC" という文字列リテラルは、{ 'A', 'B', 'C', '\0'}というcharの配列です。

構造体配列へのポインター[編集]

C言語/配列とポインタ#構造体配列のポインター』を参照してください。

メンバーに同じ型へのポインターを持つ構造体[編集]

自分自身と同じ型へのポインターを構造体のメンバーにできます。 このような構造体を自己参照構造体( self-referential structure )と呼びます。

自己参照構造体の例
#include <stdio.h>

int main(void) {
  struct smy_struct {
    struct smy_struct *next;
    int i;
    double d;
    char c;
    char str[8];
  };

  struct smy_struct a = {NULL, 123, 1.23, 'a', "abc"}, 
                    b = {&a, 456, 4.56, 'b', "def"},
                    c = {&b, 789, 7.89, 'c', "ghi"};

  for (struct smy_struct *p = &c; p; p = p->next)
    printf("%d %f %c %s\n", p->i, p->d, p->c, p->str);
}
実行結果
789 7.890000 c ghi
456 4.560000 b def
123 1.230000 a abc

上の例では、3つの構造体a、b、cのメンバーをポインター変数pを用いてトラバースしながら表示しています。

構造体と関数[編集]

C言語に、「Go/メソッドとインターフェース」の「都市間の大圏距離を求めるメソッドを追加した例」を移植してみました。

構造体と関数
#include <stdio.h>
#include <math.h>

typedef struct {
  double longitude, latitude;
} GeoCoord;

void GCPrint(const GeoCoord *gc) {
  const char *ew = "東経";
  const char *ns = "北緯";
  double lng = gc->longitude; // long はCでは予約語なので lng に
  double lat = gc->latitude;
  if (lng < 0.0) {
    ew = "西経";
    lng = -lng;
  }
  if (lat < 0.0) {
    ns = "南緯";
    lat = -lat;
  }
  printf("(%s: %f, %s: %f)", ew, lng, ns, lat);
}

double GCDistance(const GeoCoord *gc, const GeoCoord *other) {
  // C言語では円周率の値は標準では定義されていません。
  const double i = 3.1415926536 / 180; 
  const double r = 6371.008;
  return acos(sin(gc->latitude * i) * sin(other->latitude * i) +
              cos(gc->latitude * i) * cos(other->latitude * i) *
                  cos(gc->longitude * i - other->longitude * i)) * r;
}

int main(void) {
  struct {
    const char *name;
    GeoCoord gc;
  } sites[] = {
      {"東京駅", (GeoCoord){139.7673068, 35.6809591}},
      {"シドニー・オペラハウス", (GeoCoord){151.215278, -33.856778}},
      {"グリニッジ天文台", (GeoCoord){-0.0014, 51.4778}},
  };
  const int len = sizeof sites / sizeof *sites;
  for (int i = 0; i < len; i++) {
    printf("%s: ", sites[i].name);
    GCPrint(&sites[i].gc);
    printf("\n");
  }
  for (int i = 0; i < len; i++) {
    const int j = (i + 1) % len;
    printf("%s - %s: %f\n", sites[i].name, sites[j].name,
           GCDistance(&sites[i].gc, &sites[j].gc));
  }
}
実行結果
東京駅: (東経: 139.767307, 北緯: 35.680959)
シドニー・オペラハウス: (東経: 151.215278, 南緯: 33.856778)
グリニッジ天文台: (西経: 0.001400, 北緯: 51.477800)
東京駅 - シドニー・オペラハウス: 7823.269299
シドニー・オペラハウス - グリニッジ天文台: 16987.270838 
グリニッジ天文台 - 東京駅: 9560.546566
C言語に移植して

移植にあたって、変数名 long がキーワードと衝突するというマイナーなものもありますが、

  • 言語仕様にメソッドに相当する機能がない
  • 文字列を返す関数がうまく表現できない

この二点は厄介で、次のような実装上の工夫で解決しました。

言語仕様にメソッドに相当する機能がない
ないものはないので、関数の第一引数を構造体へのポインターにする関数群 GCPrint GCDistance の様に接頭辞GCを付けることで区別しました。
文字列を返す関数がうまく表現できない
C言語では文字列は第一市民ではなく、string.h のライブラリ関数もバッファの確保はプログラマの責任になります。
これが、度々脆弱性の原因になるバッファーアンダーランの原因になり細心の注意が必要です
また、strdup の様にヒープ上にオブジェクトを用意し、そのオブジェクトのアドレスを戻り値にする方法があります。
この方法はバッファアンダーランは関数側で注意すればいいのですが、呼出し側でメモリー解放を忘れるとメモリーリークの原因になるので細心の注意が必要です
細心の注意が必要なAPIは、悪いAPIなので、今回は文字列を返す関数はさっぱり諦め、標準出力に書出す関数として実装し、標準出力上で合成することにしました。


脚註[編集]

  1. ^ N2176 C17 ballot ISO/IEC 9899:2017. ISO/IEC JTC1/SC22/WG14. p. 81, §6.7.2.1 Structure and union specifiers. オリジナルの2018-12-30時点によるアーカイブ。. https://web.archive.org/web/20181230041359/http://www.open-std.org/jtc1/sc22/wg14/www/abq/c17_updated_proposed_fdis.pdf. 
  2. ^ N2176 C17 ballot ISO/IEC 9899:2017. ISO/IEC JTC1/SC22/WG14. p. 85, §6.7.2.3 Tags. オリジナルの2018-12-30時点によるアーカイブ。. https://web.archive.org/web/20181230041359/http://www.open-std.org/jtc1/sc22/wg14/www/abq/c17_updated_proposed_fdis.pdf. 
  3. ^ N2176 C17 ballot ISO/IEC 9899:2017. ISO/IEC JTC1/SC22/WG14. p. 59, §6.5.2.3 Structure and union members. オリジナルの2018-12-30時点によるアーカイブ。. https://web.archive.org/web/20181230041359/http://www.open-std.org/jtc1/sc22/wg14/www/abq/c17_updated_proposed_fdis.pdf. 
  4. ^ N2176 C17 ballot ISO/IEC 9899:2017. ISO/IEC JTC1/SC22/WG14. p. 57, §6.5.2 Postfix operators. オリジナルの2018-12-30時点によるアーカイブ。. https://web.archive.org/web/20181230041359/http://www.open-std.org/jtc1/sc22/wg14/www/abq/c17_updated_proposed_fdis.pdf. 
  5. ^ 構造体 –漏洩させないための注意点–
  6. ^ N1570 Committee Draft — April 12, 2011 ISO/IEC 9899:201x. ISO/IEC. p. 85, §6.5.2.5 Compound literals. http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf.