勉強を始める前に、五山の一つである華山の景色を眺めて良い気分を作りましょう。良い気分があってこそ、勉強はより良くなります。
目次
2.3 ビットセグメントに関するクロスプラットフォームの問題
構造
1 構造の宣言
1.1 構造の基礎知識
構造体とはメンバー変数と呼ばれる値の集合です。構造体の各メンバーは、異なる型の変数にすることができます。
1.2 構造体の宣言
struct tag { member-list; }variable-list;
たとえば、学生について説明すると次のようになります。
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}; //分号不能丢
1.3 特別な注意事項
構造体を宣言する場合、構造体は不完全になる可能性があります。例えば:
//匿名结构体类型
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20], *p;
上記2つの構造体は宣言時に構造体タグ(tag)を省略しています。それで、ここで質問が来ますか?
//上記のコードに基づくと、次のコードは合法ですか?
p = &x;
警告: コンパイラは、上記の 2 つの宣言を 2 つの完全に異なる型として扱います。したがって、それは違法です。
1.4 構造体の自己参照
構造体自体の型のメンバーを構造体に含めることはできますか?
//代码1
struct Node
{
int data;
struct Node next;
};
//可行否?
如果可以,那sizeof(struct Node)是多少?
構造変数の自己参照により無限の入れ子人形が発生するため、その答えは実現不可能です。struct Nodeのサイズを求める場合は自分自身も含めますが、自身のサイズは不明なのでこの書き方は間違っています!
正しい書き方は次のとおりです。
//代码2
struct Node
{
int data;
struct Node* next;
};
ここに別の質問があります。
typedef で名前を変更するときにポインタを自己参照しても大丈夫ですか? コードは以下のように表示されます。
typedef struct
{
int data;
Node* next;
}Node;
//这样写代码,可行否?
コードの実行順序は上から下であり、typedef の名前変更には最後に新しい名前が付けられるため、答えは「いいえ」です。これを構造内で使用するのは間違いです。具体的な解決策は以下の通りです。
typedef struct Node
{
int data;
struct Node* next;
}Node;
1.5 構造体変数の定義と初期化
構造体型を使用すると、変数を定義する方法は実際には非常に簡単です。
struct Point
{
int x;
int y;
}p1; //声明类型的同时定义变量p1(第一种)
struct Point p2; //定义结构体变量p2(第二种)
//初始化:定义变量的同时赋初值。
struct Point p3 = {x, y};(第三种)
struct Stu //类型声明
{
char name[15];//名字
int age; //年龄
};
struct Stu s = {"zhangsan", 20};//初始化
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化(第四种)
struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化(第五种)
上記の 5 つのメソッドはすべて、コードの形式で明確に示されています。
1.6 構造メモリの調整 (強調)
構造体の基本的な使い方をマスターしました。ここで、構造のサイズを計算するという問題に取り組みます。
これも特に人気のあるテスト ポイントです。構造メモリの調整です。!!
プログラムから始めましょう:
struct S1
{
char c1;
int i;
char c2;
};
int main(void)
{
printf("%d\n", sizeof(struct S1));
return 0;
}
構造体 S1 のサイズはどれくらいにすべきでしょうか? 一般的には最初は 6 だと思います
では、なぜ結果が 12 になるのでしょうか? 最初にマクロ offsetof を渡します (構造体の開始位置と比較した構造体メンバーのオフセットを計算します)。このマクロはヘッダー ファイル #include<stddef.h> にあります。
#include<stddef.h>
struct S1
{
char c1;
int i;
char c2;
};
int main(void)
{
//printf("%d\n", sizeof(struct S1));
printf("%d\n", offsetof(struct S1, c1));
printf("%d\n", offsetof(struct S1, i));
printf("%d\n", offsetof(struct S1, c2));
return 0;
}
構造体の内容はいっぱいですが、なぜこの 3 バイトを無駄にし続けるのでしょうか? ?
学びましょう:
計算方法は?
まず、構造の配置規則をマスターする必要があります。
1. 最初のメンバは、構造体変数からのオフセットが 0 のアドレスにあります。
2. その他のメンバ変数は、ある数値(アライメント番号)の整数倍のアドレスにアライメントされている必要があります。アライメント = コンパイラのデフォルトのアライメントとメンバー サイズの小さい値。VS のデフォルト値は 8 です。Linux にはデフォルトのアライメント番号はなく、アライメント番号はメンバー自体のサイズです。
3. 構造体の合計サイズは、最大アライメント番号の整数倍です (各メンバー変数にはアライメント番号があります)。
4. 構造がネストされている場合、ネストされた構造はそれ自体の最大アライメントの整数倍にアライメントされ、構造全体のサイズはすべての最大アライメント (ネストされた構造のアライメントを含む) 倍の整数になります。
上記の記憶調整ルールを学習した後は、上記のすべての質問を理解する必要があるため、次の質問を練習します。
#include<stddef.h>
struct S1
{
char c1;
char c2;
int i;
};
int main(void)
{
printf("%d\n", sizeof(struct S1));
return 0;
}
上記の構造を具体的に分析してみましょう: 結果は 8 ですか? 検証してみましょう: はい、誰もが構造のメモリ アライメントを基本的に理解し、習得していると思います。では、なぜメモリの調整が必要なのでしょうか?
理由:
1. プラットフォームの理由 (移植の理由): すべてのハードウェア プラットフォームが任意のアドレスのデータにアクセスできるわけではありません。一部のハードウェア プラットフォームでは、特定のアドレスで特定の種類のデータしかフェッチできず、そうでない場合はハードウェア例外がスローされます。
2. パフォーマンス上の理由: データ構造 (特にスタック) は、可能な限り自然な境界上に配置される必要があります。その理由は、アライメントされていないメモリにアクセスするには、プロセッサが 2 回のメモリ アクセスを行う必要があるのに対し、アライメントされたメモリ アクセスには 1 回のアクセスしか必要ないためです。
一般に、構造のメモリ調整は、空間と時間を交換する実践です。
構造を設計するときは、位置合わせを満たし、スペースを節約する必要があります。
狭いスペースにいるメンバーはできるだけ集まるようにしましょう。
//例如: struct S1 { char c1; int i; char c2; }; struct S2 { char c1; char c2; int i; };
S1 タイプと S2 タイプのメンバーはまったく同じですが、S1 と S2 が占有するスペースのサイズに若干の違いがあります。
1.7 デフォルトのアライメント番号を変更する
#pragma preprocessing ディレクティブについては以前に説明しましたが、ここではそれを再度使用してデフォルトの配置を変更します。
#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为1
struct S2
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
//输出的结果是什么?
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
return 0;
}
アライメントを 1 に設定すると、同じ構造体のメモリ サイズが 12 から 6 に変わります。
1.8 構造体パラメータの受け渡し
struct S
{
int data[1000];
int num;
};
struct S s = {
{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
print1(s); //传结构体
print2(&s); //传地址
return 0;
}
上記の print1 関数と print2 関数のどちらが優れていますか?
答えは、print2 関数の方が望ましいということです。理由:
関数がパラメータを渡すとき、パラメータをスタックにプッシュする必要があるため、時間と空間のシステム オーバーヘッドが発生します。
構造体オブジェクトが渡される場合、構造体が大きすぎるため、パラメータをスタックにプッシュするシステムのオーバーヘッドが比較的大きくなり、パフォーマンスの低下につながります。
2. ビットセグメント
2.1 ビットセグメントとは
ビット フィールドの宣言と構造は似ていますが、次の 2 つの違いがあります。
1. ビットフィールドのメンバーは、int、unsigned int、または signed int である必要があります。
2. ビットセグメントのメンバー名の後ろにコロンと数字が続きます。
struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};
Aはビットセグメントタイプです。セグメントAのサイズはどれくらいですか?
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
int main()
{
printf("%d\n", sizeof(struct A));
return 0;
}
今の質問で、下に目を向けてみましょう。
2.2 ビットセグメントのメモリ割り当て
1. ビット セグメントのメンバーは、int unsigned int signed int または char (整数ファミリーに属する) 型にすることができます。
2. ビットフィールドのスペースは、必要に応じて 4 バイト (int) または 1 バイト (char) の形式で解放されます。
3. ビットセグメントは不確定要素が多く、クロスプラットフォームではないため、移植性を重視するプログラムではビットセグメントの使用を避けてください。
例えば:
//一个例子
struct S
{
char a:3;
char b:4;
char c:5;
char d:4;
};
struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
//空间是如何开辟的?
次に分析してみましょう:
vs のビット セグメントのメモリ割り当てが一目でわかります。
2.3 ビットセグメントに関するクロスプラットフォームの問題
1. int ビット フィールドが符号付き数値として扱われるか、符号なし数値として扱われるかは未定義です。
2. ビットフィールドの最大ビット数は決定できません。(16 ビット マシンは最大 16、32 ビット マシンは最大 32、27 と記述されます。16 ビット マシンでは問題が発生します。
3. ビット セグメントのメンバーがメモリ内で左から右に割り当てられるか、右から左に割り当てられるかはまだ定義されていません。
4. 構造に 2 つのビット セグメントが含まれており、2 番目のビット セグメントが大きすぎて最初のビット セグメントの残りのビットを収容できない場合、残りのビットを破棄するか利用するかがわかりません。
概要:構造と比較すると、ビット セグメントは同じ効果を達成でき、スペースを大幅に節約できますが、クロスプラットフォームの問題があります。
3. 列挙
列挙とはその名の通り、一つずつ列挙することです。考えられるすべての値をリストします。たとえば、私たちの実生活では、性別: 男性、女性、機密、それらを 1 つずつリストすることもできます。月は 12 か月あり、1 つずつリストすることもできます。
3.1 列挙型の定義
enum Day//星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
enum Sex//性别
{
MALE,
FEMALE,
SECRET
};
enum Color//颜色
{
RED,
GREEN,
BLUE
};
上記で定義された enum Day 、 enum Sex 、および enum Color はすべて列挙型です。{} 内の内容は、列挙型の可能な値であり、列挙定数とも呼ばれます。
これらの可能な値はすべて有効で、デフォルトでは 0 から始まり、順番に 1 ずつ増加します。もちろん、列挙型を宣言するときに初期値を割り当てることもできます。
例えば:
enum Color//颜色
{
RED=1,
GREEN,
BLUE=4
};
//RED = 1;GREEN = 2; BLUE = 4;
3.2 列挙の利点
#define を使用して定数を定義できますが、なぜ列挙型を使用するのでしょうか?
列挙の利点: 1. コードの可読性と保守性が向上します。 2. #define で定義された識別子と比較して、列挙にはより厳密な型チェックがあります。3. デバッグが簡単 4. 使いやすく、一度に複数の定数を定義できる
3.3 列挙型の使用
enum Color//颜色
{
RED=1,
GREEN=2,
BLUE=4
};
enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
clr = 5; //ok??
4. ユニオン(コミュニティ)
4. ユニオン(コミュニティ)
Union も特別なカスタム型であり、この型で定義される変数には一連のメンバーが含まれており、これらのメンバーが同じ空間を共有することが特徴です (したがって、union は Union とも呼ばれます)。例えば:
union Un
{
char c;
int i;
};
int main()
{
union Un un = { 0 };
printf("%d\n", sizeof(un));
printf("%p\n", &un);
printf("%p\n", &(un.i));
printf("%p\n", &(un.c));
return 0;
}
共用体のどの部分にアクセスしても、アドレスは同じです。つまり、共用体の変数は同じメモリ空間を共有し、各変数には誰も割り当てられず、メモリ サイズは 4 です。組合内で最大の規模。
したがって、同時に使用できるのは共用体内の 1 つの要素のみです。使用しないと干渉します。
コードの一部を使用してそれを証明できます。
union Un
{
char c;
int i;
};
int main()
{
union Un un = { 0 };
un.i = 0x11223344;
un.c = 0x55;
return 0;
}
4.2 組合の特徴
共用体のメンバーは同じメモリ空間を共有するため、そのようなジョイント変数のサイズは少なくとも最大のメンバーのサイズになります (共用体には少なくとも最大のメンバーを保存する機能が必要であるため)。
4.3 ジョイントサイズの計算
共用体のサイズは、少なくとも最大のメンバーのサイズとなります。
最大メンバー サイズが最大アライメントの整数倍ではない場合、最大アライメントの整数倍にアライメントする必要があります。
練習しましょう:
union Un1
{
char c[5];
int i;
};
//下面输出的结果是什么?
printf("%d\n", sizeof(union Un1));
結果はどうなりましたか?
とりあえずはここまでです、ご視聴ありがとうございました!!!!