目次 †
構造体 †
- C言語では、構造体のメンバーは宣言順に並べられ、最初の方のメンバーが必ず低い方のアドレスを持つ。
ポインタを介した構造体の利用 †
- メリット
- realloc時のコピーを最小限にする。
- 構造体をソートするときにアドレスの入れ替えだけで済む。
- デメリット
- 配列の各要素に対してmallocを行う必要があったり、構造体のデータが必要なくなった時点でfreeする必要があったり処理が複雑になる。
qsortで構造体のソート †
標準関数で用意されているqsort関数を用いることで構造体をソートすることができる。その際構造体の比較関数を用意する必要がある。
例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
-
|
|
|
!
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-
|
!
|
!
-
|
|
|
|
|
!
| #include <stdio.h>
#include <string.h>
struct person{
char name[20];
char address[40];
char tel[20];
};
int main(void){
i =;
n = 2;
struct person **persons;
persons = malloc(sizeof(struct person*) * n);
persons[0] = malloc(sizeof(struct person));
strcpy(persons[0]->name, "IPUSIRON");
strcpy(persons[0]->address, "Security Akademeia");
strcpy(persons[0]->tel, "090-XXXX-XXXX");
persons[1] = malloc(sizeof(struct person));
strcpy(persons[1]->name, "John");
strcpy(persons[1]->address, "Japan");
strcpy(persons[1]->tel, "090-YYYY-YYYY");
sort(persons, n, sizeof(struct person*), person_cmp);
for (i = 0; i < n; i++) {
printf("%s %s\n", persons[i]->name, persons->[i].tel);
}
return 0;
}
int person_cmp(const void *a, const void *b){
struct person *pa = *(struct person**)a;
struct person *pb = *(struct person**)b;
return strcmp(pa->tel, pb->tel);
}
|
構造体のテクニック †
- 構造体タグ名を他の関数でも使いたいなら、構造体タグの定義をすべての関数より外で記述しておく。
- 別の関数で初期化した構造体変数を、別の関数で間接参照したければ、ポインタを渡す。ただし、呼び出される関数側は引数に構造体の型を指定する。
- さらに、通常の変数の間接参照では「*ポインタ変数」としてアクセスしていたが、構造体の場合はアロー演算子を利用して「ポインタ変数->構造体メンバ」としてアクセスする。
memsetを利用した構造体変数の初期化 †
構造体変数を初期化する再に、構造体の各面罵をいちいち初期化するのではなく、memsetを利用してまとめて初期化することができる。
memset関数の第1引数はアドレス、第2引数は初期化する値、第3引数は初期化するサイズを指定する。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
-
|
|
|
!
-
|
|
|
!
| #include <stdio.h>
#include <string.h>
struct p_person{
char *name;
char *address;
char *tel;
};
int main(void){
struct p_person person;
memset(&person, '\0', sizeof(person));
return 0;
}
|
[補講]memsetを使った初期化の方法は構造体だけでなく、配列のような連続するメモリ空間ならば利用できる。 ◇
ヌルポインタはアドレス0を指すポインタである。しかし、ヌルポインタのアドレスが内部的に0ではない処理系も存在する。
memsetはデータを単なるバイト列として扱う。構造体や配列を初期化するためにmemsetで初期化を行う場合でも、メンバという概念は無視され、バイト単位で0に初期化していく。
ポインタが内部的に0でない処理系では、コンパイラがアドレスとして0の場合に限り何らかの変換を行う。しかし、memsetにはこうした変換を行うことができない。なぜならば、アドレスとしての0を代入することにならないからである。
同様の問題は浮動小数点型でも起こる。ビットがすべて0のデータが数値として0.0でない処理系が存在するのである。
以上より、移植を優先したいならば、memsetを使った構造体変数の初期化は止めておいた方がよい。
strdup †
- 引数に与えられた文字列のコピーをmallocした領域に作成して、そのアドレスを返す関数である。
- 標準ライブラリの関数で多くの処理系ではstrcpyなどと一緒に含まれているが、ANSいでは規定されていない。
- strdupをうまく利用することで、ポインタの構造体メンバに値を入れることができる。
例1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
-
|
|
|
!
-
|
|
|
|
|
!
| #include <stdio.h>
#include <string.h>
struct p_person{
char *name;
char *address;
char *tel;
};
int main(void){
struct p_person person;
person.name = strdup("IPUSIRON");
person.address = strdup("Security Akademeia");
person.tel = strdup("090-XXXX-XXXX");
return 0;
}
|
例2:構造体オブジェクトへのポインタからメンバにアクセスする
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
-
|
|
|
!
-
|
|
|
|
|
|
!
| #include <stdio.h>
#include <string.h>
struct p_person{
char *name;
char *address;
char *tel;
};
int main(void){
struct p_person *person;
person = malloc(sizeof(struct p_person);
person->name = strdup("IPUSIRON");
person->address = strdup("Security Akademeia");
person->tel = strdup("090-XXXX-XXXX");
return 0;
}
|
[補講]mallocの戻り値はvoid*型なので、構造体へのポインタに代入することができる。 ◇
strdupのコード †
strdupのコードは次の通りである。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
-
|
-
|
!
-
|
!
|
!
| #include <stdio.h>
#include <stdlib.h>
char *strdup(const char *src){
char *p;
if (src == NULL) {
return NULL;
}
if ((p = malloc((strlen(src) + 1) * sizeof(char)) != NULL) {
strcpy(p, src);
}
return p;
}
|
オフセット †
各メンバーの先頭からのオフセット(先頭から何バイト離れているか)を厳密に知りたい場合は、offsetofマクロを利用する。
このoffsetofマクロはstddef.hで定義されている。もしoffsetofがサポートされていない処理系であれば、次のコードを記述しておけばよい。
#define offsetof(s, mem) (size_t) & (((s*)0)->mem)
例:構造体メンバーのオフセットの表示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
-
|
|
|
!
-
|
|
|
!
| #include <stdio.h>
#include <stddef.h>
struct test{
char c1;
int x;
char c2;
}
int main(void){
printf("c1のオフセット : %u\n", offsetof(struct test, c1));
printf("xのオフセット : %u\n", offsetof(struct test, x));
printf("c2のオフセット : %u\n", offsetof(struct test, c2));
}
|
構造体の不完全型 †
次のように宣言すれば、具体的な型が不明であるが、pointがとりあえず構造体型であること(構造体型のpointの存在)を示す。
struct point;
この宣言により、具体的な構造体を定義しなくも、struct point型の変数や別の構造体メンバーを定義することができる。
構造体のアライメント †
- 同一型の構造体の代入を行う際には、ビット単位のコピーではなく、メンバー単位のコピーが行われる。
- 例えば、「struct test x, y;」と定義された2つのオブジェクトx,yを考える。このとき、「x = y;」と構造体の代入を行ったときに、yの各メンバーがxの各メンバーにコピーされる。ここで、yがワード境界であってパディングが存在する場合、そのパディングの部分(ビットパターン)はxにコピーされるとは限らないとANSIで定義されている。
- こうした特徴を持つため、C言語では構造体x,yに対して、「x == y」や「x != y」のような比較ができないのである。
- よって、コンパイラのオプションがバイト境界でもワード境界でも問題がないように、プログラム側で構造体の大きさが一致するように、メンバーの順番を変えたり、文字や文字配列のダミーメンバーを使うとよい。特に外部記憶装置にメモリイメージを読み書きするシステムではそうである。
例:ここではintは2バイトと仮定する。
(×)
struct test{
char c1;
int x;
char c2;
}
このときのバイト(1バイト)境界配置のアライメントは4バイト(=1+2+1)になり、ワード(2バイト)境界配置のアライメントは6バイト(=1+1(パディング)+2+1+1(パディング))になる。
(図を描く)struct1
よって、コンパイラの境界配置オプションの違いにより、占有するメモリ空間が異なるようになってしまう。
一方、メンバーの番地が偶数番地のときに高速処理ができるシステムであったとすると、ワード境界配置時のときがc1,x,c2に対してアクセスが早いことになる。それに対して、バイト強化配置時のときはc1,c2は偶数番地であるが、xは奇数番地から始まっておりアクセスが遅くなってしまう。
(○)
struct test{
char c1;
char c2;
int x;
}
このようにメンバーをうまく並べることにより、バイト境界配置時とワード境界配置時の占有するメモリの量を同一にすることができる。さらに、メンバーの番地が奇数番地のときに高速処理ができるシステムであれば、いずれも場合でもxのアクセス速度が変わらない。
参考文献 †