اتحادیهها (Unions)
اتحادیهها یکی از انواع داده مشتقشده در زبان C هستند که از نظر سینتکس بسیار شبیه به ساختارها عمل میکنند، اما تفاوت اساسی در نحوه ذخیرهسازی در حافظه دارند. در یک اتحادیه، همه اعضا یک مکان حافظه مشترک را به اشتراک میگذارند و در هر لحظه فقط یکی از اعضا میتواند معتبر باشد. این ویژگی باعث میشود اتحادیهها حافظه بسیار کارآمدی داشته باشند.
تفاوت کلیدی Union با Struct
| ویژگی | ساختار (struct) | اتحادیه (union) |
|---|---|---|
| ذخیرهسازی | هر عضو فضای جداگانهای در حافظه دارد | همه اعضا یک فضای مشترک دارند |
| اندازه | مجموع اندازه اعضا (بهعلاوه padding) | برابر با بزرگترین عضو |
| دسترسی همزمان | همه اعضا بهطور همزمان معتبر هستند | فقط یک عضو در هر لحظه معتبر است |
| کاربرد | گروهبندی دادههای مرتبط | ذخیرهسازی یکی از چند نوع داده مختلف |
تعریف اتحادیه
سینتکس تعریف اتحادیه مشابه ساختار است، با این تفاوت که از کلمه کلیدی union استفاده میشود:
union Data { int i; float f; char str[20]; };
در این مثال، اتحادیه Data سه عضو دارد: یک عدد صحیح، یک عدد اعشاری و یک رشته. اندازه این اتحادیه برابر با بزرگترین عضو یعنی str[20] (۲۰ بایت) خواهد بود.
تعریف و استفاده از متغیرهای union
#include <stdio.h> #include <string.h> union Data { int i; float f; char str[20]; }; int main() { union Data data; // استفاده از عضو int data.i = 10; printf("data.i: %d\n", data.i); printf("data.f: %f\n", data.f); // نامعتبر - دادههای تصادفی // استفاده از عضو float data.f = 220.5; printf("data.f: %f\n", data.f); printf("data.i: %d\n", data.i); // نامعتبر - دادههای تصادفی // استفاده از عضو string strcpy(data.str, "Hello"); printf("data.str: %s\n", data.str); printf("data.i: %d\n", data.i); // نامعتبر - دادههای تصادفی return 0; }
نکته مهم: وقتی به یک عضو مقدار میدهید، مقدار عضو قبلی از بین میرود. در مثال بالا، وقتی
data.f = 220.5را انجام میدهیم، مقدارdata.iاز بین میرود.
اندازه اتحادیه
اندازه یک اتحادیه برابر با بزرگترین عضو آن است (بهعلاوه padding احتمالی):
#include <stdio.h> union Data { int i; // ۴ بایت float f; // ۴ بایت char str[20]; // ۲۰ بایت }; struct DataStruct { int i; // ۴ بایت float f; // ۴ بایت char str[20]; // ۲۰ بایت }; int main() { printf("اندازه union: %zu\n", sizeof(union Data)); // معمولاً ۲۰ printf("اندازه struct: %zu\n", sizeof(struct DataStruct)); // معمولاً ۲۸ return 0; }
| نوع | اعضا | اندازه تقریبی |
|---|---|---|
union Data | int i, float f, char str[20] | ۲۰ بایت |
struct DataStruct | int i, float f, char str[20] | ۲۸ بایت |
مقداردهی اولیه اتحادیه
مقداردهی به اولین عضو:
union Data data = {10}; // عضو اول (int) مقدار ۱۰ میگیرد
مقداردهی با نام عضو (C99):
union Data data = {.f = 3.14}; // عضو float مقدار ۳.۱۴ میگیرد
مقداردهی به رشته:
union Data data = {.str = "Hello"}; // عضو string مقدار میگیرد
کاربردهای عملی اتحادیهها
۱. ذخیرهسازی انواع مختلف داده در یک متغیر
#include <stdio.h> #include <string.h> typedef enum { INT_TYPE, FLOAT_TYPE, STRING_TYPE } DataType; typedef union { int intValue; float floatValue; char stringValue[50]; } DataValue; typedef struct { DataType type; DataValue value; } FlexibleData; void printData(FlexibleData data) { switch (data.type) { case INT_TYPE: printf("عدد صحیح: %d\n", data.value.intValue); break; case FLOAT_TYPE: printf("عدد اعشاری: %f\n", data.value.floatValue); break; case STRING_TYPE: printf("رشته: %s\n", data.value.stringValue); break; } } int main() { FlexibleData data1 = {INT_TYPE, .value.intValue = 42}; FlexibleData data2 = {FLOAT_TYPE, .value.floatValue = 3.14}; FlexibleData data3 = {STRING_TYPE, .value.stringValue = "Hello World"}; printData(data1); printData(data2); printData(data3); return 0; }
۲. سیستم ذخیرهسازی پیکربندی
#include <stdio.h> #include <string.h> typedef enum { CONFIG_INT, CONFIG_FLOAT, CONFIG_STRING } ConfigType; typedef union { int intVal; float floatVal; char strVal[100]; } ConfigValue; typedef struct { char name[50]; ConfigType type; ConfigValue value; } Config; void printConfig(Config config) { printf("%s: ", config.name); switch (config.type) { case CONFIG_INT: printf("%d\n", config.value.intVal); break; case CONFIG_FLOAT: printf("%.2f\n", config.value.floatVal); break; case CONFIG_STRING: printf("%s\n", config.value.strVal); break; } } int main() { Config configs[3] = { {"max_users", CONFIG_INT, .value.intVal = 100}, {"timeout", CONFIG_FLOAT, .value.floatVal = 5.5}, {"server_name", CONFIG_STRING, .value.strVal = "localhost"} }; for (int i = 0; i < 3; i++) { printConfig(configs[i]); } return 0; }
۳. تبدیل نوع داده (Type Punning)
یکی از کاربردهای پیشرفته اتحادیهها، دسترسی به نمایش باینری یک نوع داده بهعنوان نوع دیگر است:
#include <stdio.h> union FloatBits { float f; unsigned int bits; }; int main() { union FloatBits fb; fb.f = 3.14; printf("عدد اعشاری: %f\n", fb.f); printf("نمایش باینری: 0x%08X\n", fb.bits); printf("بایتها: "); unsigned char* bytes = (unsigned char*)&fb.bits; for (int i = 0; i < sizeof(float); i++) { printf("%02X ", bytes[i]); } printf("\n"); return 0; }
نکته: استفاده از union برای type punning در C استاندارد است اما در C++ رفتار نامشخصی دارد.
اتحادیههای تو در تو
اتحادیهها میتوانند شامل ساختارها یا اتحادیههای دیگر باشند:
typedef struct { int x; int y; } Point; typedef union { Point point; int coordinates[2]; } CoordUnion; int main() { CoordUnion cu; cu.point.x = 10; cu.point.y = 20; printf("x: %d, y: %d\n", cu.point.x, cu.point.y); printf("x: %d, y: %d\n", cu.coordinates[0], cu.coordinates[1]); return 0; }
اتحادیههای ناشناس (Anonymous Unions)
C11 به بعد از اتحادیههای ناشناس پشتیبانی میکند که بدون نام تعریف میشوند و اعضای آنها مستقیماً در ساختار والد قابل دسترسی هستند:
#include <stdio.h> typedef struct { int id; union { int intData; float floatData; char stringData[50]; }; // اتحادیه ناشناس } DataContainer; int main() { DataContainer data; data.id = 1; data.intData = 100; // دسترسی مستقیم به عضو union printf("ID: %d, داده: %d\n", data.id, data.intData); data.floatData = 3.14; printf("ID: %d, داده: %.2f\n", data.id, data.floatData); return 0; }
مقایسه Union با Struct در حافظه
نمایش حافظه برای یک ساختار و اتحادیه مشابه:
#include <stdio.h> struct DataStruct { int i; // ۴ بایت char c; // ۱ بایت double d; // ۸ بایت }; union DataUnion { int i; // ۴ بایت char c; // ۱ بایت double d; // ۸ بایت }; int main() { printf("اندازه struct: %zu\n", sizeof(struct DataStruct)); // معمولاً ۱۶ printf("اندازه union: %zu\n", sizeof(union DataUnion)); // ۸ struct DataStruct s = {10, 'A', 3.14}; union DataUnion u; u.d = 3.14; printf("آدرسهای struct:\n"); printf("i: %p\n", (void*)&s.i); printf("c: %p\n", (void*)&s.c); printf("d: %p\n", (void*)&s.d); printf("آدرسهای union:\n"); printf("i: %p\n", (void*)&u.i); printf("c: %p\n", (void*)&u.c); printf("d: %p\n", (void*)&u.d); return 0; }
نکات حرفهای و هشدارها
هشدار: خواندن عضوی که مقداردهی نشده
union Data { int i; float f; }; int main() { union Data d; d.i = 10; printf("%f\n", d.f); // رفتار نامشخص - دادههای تصادفی return 0; }
استفاده از Tag (پرچم) برای تشخیص عضو معتبر
typedef struct { enum { INT_VAL, FLOAT_VAL, STR_VAL } tag; union { int intVal; float floatVal; char stringVal[50]; } value; } SafeUnion; void printSafeUnion(SafeUnion su) { switch (su.tag) { case INT_VAL: printf("%d\n", su.value.intVal); break; case FLOAT_VAL: printf("%f\n", su.value.floatVal); break; case STR_VAL: printf("%s\n", su.value.stringVal); break; } }
خطاهای رایج با اتحادیهها
۱. فراموشی اینکه فقط یک عضو معتبر است:
union Data d; d.i = 10; d.f = 3.14; // مقدار i از بین میرود printf("%d", d.i); // مقدار تصادفی
۲. استفاده از union با اشارهگرها بدون احتیاط:
union Data d; d.i = 10; int* ptr = &d.i; d.f = 3.14; // اشارهگر ptr نامعتبر میشود printf("%d", *ptr); // رفتار نامشخص
۳. کپی کردن unionها با اعضای اشارهگر:
union Data { int* ptr; char buffer[100]; };
خلاصه نکات کلیدی
| مفهوم | توضیح |
|---|---|
| تعریف | union Data { int i; float f; }; |
| ذخیرهسازی | همه اعضا یک مکان حافظه مشترک دارند |
| اندازه | برابر با بزرگترین عضو |
| دسترسی همزمان | فقط یک عضو در هر لحظه معتبر است |
| مقداردهی | {10} (عضو اول) یا {.f = 3.14} (با نام عضو) |
| کاربرد اصلی | صرفهجویی در حافظه، ذخیرهسازی یکی از چند نوع داده |
| محدودیت | نیاز به نگهداری tag برای تشخیص عضو معتبر |
| پیشرفتگی نسبت به struct | استفاده بهینه از حافظه |
| معایب | احتمال خطا در تشخیص عضو معتبر |