[C言語] - カスタム型の詳細説明: 構造体、列挙体、共用体

皆さん、こんにちは。今日は、C 言語のカスタム型である構造体、列挙型、共用体、さらには構造体のメモリ アライメントやビット セグメントなど、これまで知らなかった知識ポイントを共有したいと思います。

1. 構造

構造体とはメンバー変数と呼ばれる値の集合です。構造体の各メンバーは、異なる型の変数にすることができます。

構造の宣言: たとえば、生徒について次のように説明します。

struct Stu
{
    
    
 char name[20];//名字
 int age;//年龄
 char sex[5];//性别
 char id[20];//学号
}; //分号不能丢

これにより、学生の構造タイプが正常に定義されます。

特殊な構造体の宣言:
構造体を宣言する場合、不完全に宣言することができます。

//匿名结构体类型:结构在声明的时候省略掉了结构体标签
struct
{
    
    
 int a;
 char b;
 float c;
}x;
struct
{
    
    
 int a;
 char b;
 float c;
}a[20], *p;

注: // 上記のコードに基づくと、次のコードは合法ですか?
p = &x;
コンパイラは、上記の 2 つの宣言を 2 つの完全に異なる型として扱います。
したがって、それは違法です。

構造体の自己参照

//正确的自引用
struct Node
{
    
    
 int data;
 struct Node* next;
};
//错误的自引用,促使无法确定结构体的大小
struct Node
{
    
    
 int data;
 struct Node next;
};

解決:

typedef struct Node
{
    
    
 int data;
 struct Node* next;
}Node;

構造体変数の定義と初期化

構造体型を使用すると、変数の定義方法が実際には非常に簡単になります。

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};//结构体嵌套初始化

構造メモリの調整:
構造の基本的な使い方を習得しました。
次に、構造のサイズを計算するという問題を詳しく見てみましょう。
これも特によく知られている知識ポイントです: 構造メモリの調整

以下のコードを見てみましょう。構造全体のサイズについての第一印象はどうですか?

struct S1
{
    
    
 char c1;
 int i;
 char c2;
};

ここに画像の説明を挿入します

2 文字と整数のサイズは 6 バイトであってはなりません。実行すると 12 バイトになるのはなぜですか? 状況は何ですか? ここでは、学習構造の記憶の調整に関する知識を理解する必要があります。

まず、構造体のメモリ アライメント ルールを開いてみましょう。

  1. 最初のメンバーは、構造体変数からのオフセット 0 にあります。
  2. 他のメンバ変数は、特定の数(アライメント番号)の整数倍のアドレスにアライメントされる必要があります。
    アライメント番号 = コンパイラのデフォルトのアライメント番号とメンバーのサイズの小さい方。
    VS のデフォルト値は 8 です。
  3. 構造体の合計サイズは、最大アライメント番号の整数倍です (各メンバー変数にはアライメント番号があります)。
  4. 構造がネストされている場合、ネストされた構造はそれ自体の最大アライメント数の整数倍にアライメントされ、
    構造全体のサイズ最大アライメント数 (ネストされた構造のアライメント数を含む) の整数倍になります。

ここに画像の説明を挿入します
この図を見れば、構造体のメモリ アラインメントがどのようなものかを大まかに理解でき、サイズが 12 である理由も理解できるようです。

入れ子構造のサイズを見てみましょう。

struct S4
{
    
    
 char c1;
 struct S1 s1;
 double d;
};
printf("%d\n", sizeof(struct S4));

ここに画像の説明を挿入します

分析を見てみましょう:
ここに画像の説明を挿入します
なぜ構造メモリの調整が行われるのかについては:

ほとんどの参考資料には次のように書かれています。

  1. プラットフォームの理由 (移植の理由):
    すべてのハードウェア プラットフォームが任意のアドレスのデータにアクセスできるわけではありません。一部のハードウェア プラットフォームは、
    特定のアドレスで特定の種類のデータしかフェッチできません。そうでない場合は、ハードウェア例外がスローされます。
  1. パフォーマンス上の理由:
    データ構造 (特にスタック) は、可能な限り自然な境界に揃える必要があります。
    その理由は、アライメントされていないメモリにアクセスするには、プロセッサが 2 回のメモリ アクセスを行う必要があるのに対し、アライメントされたメモリ アクセスには 1 回のアクセスだけで済むためです

構造を設計する際には、配置を満足するだけでなく、スペースを節約することも考慮し、
できるだけスペースをとらない部材を集める必要があります。

上記のメモリ アライメントの質問では、デフォルトのアライメント番号が言及されていますが、これは変更できます。

#pragma 前処理ディレクティブについて説明しましたが、ここでもそれを再度使用して、デフォルトのアライメント番号を変更します。

#include <stdio.h>

#pragma pack(1)//设置默认对齐数为8
struct S2
{
    
    
	char c1;
	int i;
	char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
//在这个区间里面默认对齐数就是1啦

2. ポジションセグメント

構造について話した後は、ビット セグメントを実装する構造の機能について話さなければなりません。
ビット
セグメントとは何ですか? ビット セグメントの宣言と構造は似ていますが、次の 2 つの違いがあります:
1. ビット セグメントのメンバーは int、unsigned int、または signed int でなければなりません。
2. ビットフィールドのメンバー名の後ろにコロンと数字が続きます。

ビットセグメントの定義は次のとおりです。

struct A
{
    
    
 int _a:2;//_a占2个比特位
 int _b:5;
 int _c:10;
 int _d:30;
};

Aはビットセグメントタイプです。
セグメントAのサイズはどれくらいですか? コロンの後のビットは、対応する変数が占めるビットです。
ここに画像の説明を挿入します

ビットセグメントのメモリ割り当て:

  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;
};
int main()
{
    
    struct S s = {
    
    0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;}


ここに画像の説明を挿入します

ビットセグメントに関するクロスプラットフォームの問題

1. int ビットフィールドを符号付き数とみなすか、符号なし数とみなすかは未定義です。
2.ビットフィールドの最大ビット数は決定できません。(16 ビット マシンの最大数は 16 で、32 ビット マシンの最大数は 32 で、これは 27 と記述されます。これにより、
16 ビット マシンでは問題が発生します
ビット セグメントがメモリ内で左から右に割り当てられているか、右から左への割り当てがまだ定義されていない場合 . 4.
構造体に 2 つのビット フィールドが含まれており、2 番目のビット フィールドのメンバーが大きく、構造体の残りのビットを収容できない場合最初のビットフィールドでは、残りのビットを破棄するか使用するかが不明です

ビット フィールドには、データ送信時のデータのパッケージ化に特定の用途があります。
ここに画像の説明を挿入します
3. 列挙

列挙とはその名の通り、項目を一つずつ列挙することです。
可能な値を 1 つずつリストします。
たとえば、私たちの現実の生活では、
月曜日から日曜日までは 1 週間のうち 7 日間という限られた日数であり、それらを 1 つずつリストすることができます。
性別には、男性、女性、秘密、などを 1 つずつリストすることもできます。

enum Day//星期
{
    
    
 Mon,
 Tues,
 Wed,
 Thur,
 Fri,
 Sat,
 Sun
};


enum Color//颜色
{
    
    
 RED,
 GREEN,
 BLUE
};

上記で定義された列挙型 Day と列挙型 Color はすべて列挙型です。{} 内の内容は、列挙型の可能な値であり、列挙定数とも呼ばれます (変更できません)。
これらの可能な値はすべて有効で、デフォルトでは 0 から始まり、一度に 1 ずつ増加します。
ここに画像の説明を挿入します
もちろん定義時に初期値を与えることも可能です。例えば

enum Color//颜色
{
    
    
 RED=1,
 GREEN=2,
 BLUE=4
};

列挙型が存在できるため、それ自体の利点があるはずです。

コードの可読性と保守性の向上
#define で定義された識別子と比較して、列挙型にはより厳密な型チェックが行われます。
名前付け汚染 (カプセル化) を防止し
、デバッグを容易にします。
使いやすく、一度に複数の定数を定義できます。

列挙型の使用:

enum Color//颜色
{
    
    
	RED=1 ,
	GREEN = 2,
	BLUE = 4
};
int main()
{
    
    
	//printf("%d\n", sizeof(union Un1));
	enum Color col=RED ;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异,枚举常量实质就是整型。
	col = 3.8;
	printf("%d", col);
}

4. ユニオン(共通)本体

共用体のメンバーは同じメモリ空間を共有するため、共用体変数のサイズは少なくとも最大のメンバーのサイズでなければなりません (共用体は少なくとも最大のメンバーを格納できなければならないため)。

union Un
{
    
    
	int i;
	char c;
};
union Un un;

int main()
{
    
    
	printf("%p\n", &(un.i));
	printf("%p\n", &(un.c));
	un.i = 0x11223344;
	un.c = 0x55;
	printf("%x\n", un.i);
}

ここに画像の説明を挿入します

ユニオンサイズの計算

共用体のサイズは、少なくとも最大のメンバーのサイズとなります。
最大メンバー サイズが最大アライメント数の整数倍ではない場合、最大アライメント数の整数倍にアライメントする必要があります。

union Un1
{
    
    
	char c[5];
	int i;
};
int main()
{
    
    
	printf("%d\n", sizeof(union Un1));
	
}


ここに画像の説明を挿入します

分析: 共用体のサイズは、まず最大のメンバーのサイズであり、c[5] は 5 バイトを占め、配列 c[] の各要素のサイズは 1、vs のデフォルトのアライメント数は 8、次にそのアライメント番号は 1、i は 4 バイトを占有し、vs のデフォルトのアライメント番号は 8、そのアライメント番号は 4、最大アライメント番号は 4、整数にアライメントされた後の共用体のサイズは 8 です。 4の倍数

コンソーシアムの用途:

ここに画像の説明を挿入します
複数の項目を含む構造体を定義する場合、さまざまな項目の特定のプロパティを共用体タイプとして定義でき、スペースを大幅に節約できます。

本日の内容はここでシェアさせていただきますので、皆様のお役に立てれば幸いです、一緒に前進していきましょう!

おすすめ

転載: blog.csdn.net/m0_71214261/article/details/133272071