Câu trả lời:
Các hiệp hội thường được sử dụng để chuyển đổi giữa các biểu diễn nhị phân của số nguyên và số float:
union
{
int i;
float f;
} u;
// Convert floating-point bits to integer:
u.f = 3.14159f;
printf("As integer: %08x\n", u.i);
Mặc dù đây là hành vi không được xác định về mặt kỹ thuật theo tiêu chuẩn C (bạn chỉ được đọc trường được viết gần đây nhất), nhưng nó sẽ hoạt động theo cách được xác định rõ trong hầu hết mọi trình biên dịch.
Các hiệp hội đôi khi cũng được sử dụng để thực hiện giả đa hình trong C, bằng cách đưa ra một cấu trúc một số thẻ chỉ ra loại đối tượng mà nó chứa, sau đó kết hợp các loại có thể với nhau:
enum Type { INTS, FLOATS, DOUBLE };
struct S
{
Type s_type;
union
{
int s_ints[2];
float s_floats[2];
double s_double;
};
};
void do_something(struct S *s)
{
switch(s->s_type)
{
case INTS: // do something with s->s_ints
break;
case FLOATS: // do something with s->s_floats
break;
case DOUBLE: // do something with s->s_double
break;
}
}
Điều này cho phép kích thước struct S
chỉ là 12 byte, thay vì 28.
Các hiệp hội đặc biệt hữu ích trong lập trình nhúng hoặc trong các tình huống cần truy cập trực tiếp vào phần cứng / bộ nhớ. Đây là một ví dụ tầm thường:
typedef union
{
struct {
unsigned char byte1;
unsigned char byte2;
unsigned char byte3;
unsigned char byte4;
} bytes;
unsigned int dword;
} HW_Register;
HW_Register reg;
Sau đó, bạn có thể truy cập reg như sau:
reg.dword = 0x12345678;
reg.bytes.byte3 = 4;
Endianness (thứ tự byte) và kiến trúc bộ xử lý tất nhiên là quan trọng.
Một tính năng hữu ích khác là công cụ sửa đổi bit:
typedef union
{
struct {
unsigned char b1:1;
unsigned char b2:1;
unsigned char b3:1;
unsigned char b4:1;
unsigned char reserved:4;
} bits;
unsigned char byte;
} HW_RegisterB;
HW_RegisterB reg;
Với mã này, bạn có thể truy cập trực tiếp một bit trong địa chỉ thanh ghi / bộ nhớ:
x = reg.bits.b2;
Lập trình hệ thống cấp thấp là một ví dụ hợp lý.
IIRC, tôi đã sử dụng các hiệp hội để phân tích các thanh ghi phần cứng thành các bit thành phần. Vì vậy, bạn có thể truy cập vào một thanh ghi 8 bit (như đã có, vào ngày tôi đã làm điều này ;-) vào các bit thành phần.
(Tôi quên cú pháp chính xác nhưng ...) Cấu trúc này sẽ cho phép một thanh ghi điều khiển được truy cập dưới dạng control_byte hoặc thông qua các bit riêng lẻ. Điều quan trọng là phải đảm bảo các bit ánh xạ tới các bit thanh ghi chính xác cho một tuổi thọ nhất định.
typedef union {
unsigned char control_byte;
struct {
unsigned int nibble : 4;
unsigned int nmi : 1;
unsigned int enabled : 1;
unsigned int fired : 1;
unsigned int control : 1;
};
} ControlRegister;
Tôi đã thấy nó trong một vài thư viện như là một sự thay thế cho sự kế thừa hướng đối tượng.
Ví dụ
Connection
/ | \
Network USB VirtualConnection
Nếu bạn muốn "lớp" Kết nối là một trong những thứ trên, bạn có thể viết một cái gì đó như:
struct Connection
{
int type;
union
{
struct Network network;
struct USB usb;
struct Virtual virtual;
}
};
Ví dụ sử dụng trong libinfinity: http://git.0x539.de/?p=infinote.git;a=blob;f=libinfinity/common/inf-session.c;h=3e887f0d63bd754c6b5ec232948027cbbf4d
Các hiệp hội cho phép các thành viên dữ liệu loại trừ lẫn nhau để chia sẻ cùng một bộ nhớ. Điều này khá quan trọng khi bộ nhớ khan hiếm hơn, chẳng hạn như trong các hệ thống nhúng.
Trong ví dụ sau:
union {
int a;
int b;
int c;
} myUnion;
Liên minh này sẽ chiếm không gian của một int, thay vì 3 giá trị int riêng biệt. Nếu người dùng đặt giá trị của a , sau đó đặt giá trị của b , nó sẽ ghi đè lên giá trị của a vì cả hai đều chia sẻ cùng một vị trí bộ nhớ.
Rất nhiều công dụng. Chỉ cần làm grep union /usr/include/*
hoặc trong các thư mục tương tự. Hầu hết các trường hợp union
được bọc trong một struct
và một thành viên của cấu trúc cho biết phần tử nào trong liên minh sẽ truy cập. Ví dụ kiểm tra man elf
để thực hiện cuộc sống thực.
Đây là nguyên tắc cơ bản:
struct _mydata {
int which_one;
union _data {
int a;
float b;
char c;
} foo;
} bar;
switch (bar.which_one)
{
case INTEGER : /* access bar.foo.a;*/ break;
case FLOATING : /* access bar.foo.b;*/ break;
case CHARACTER: /* access bar.foo.c;*/ break;
}
Đây là một ví dụ về sự kết hợp từ cơ sở mã của riêng tôi (từ bộ nhớ và diễn giải nên có thể không chính xác). Nó được sử dụng để lưu trữ các yếu tố ngôn ngữ trong một trình thông dịch do tôi xây dựng. Ví dụ: đoạn mã sau:
set a to b times 7.
bao gồm các yếu tố ngôn ngữ sau:
Các yếu tố ngôn ngữ được định nghĩa là #define
các giá trị '', do đó:
#define ELEM_SYM_SET 0
#define ELEM_SYM_TO 1
#define ELEM_SYM_TIMES 2
#define ELEM_SYM_FULLSTOP 3
#define ELEM_VARIABLE 100
#define ELEM_CONSTANT 101
và cấu trúc sau đây được sử dụng để lưu trữ từng phần tử:
typedef struct {
int typ;
union {
char *str;
int val;
}
} tElem;
sau đó kích thước của mỗi phần tử là kích thước của liên kết tối đa (4 byte cho loại và 4 byte cho liên kết, mặc dù đó là các giá trị điển hình, kích thước thực tế cho phép thực hiện).
Để tạo phần tử "set", bạn sẽ sử dụng:
tElem e;
e.typ = ELEM_SYM_SET;
Để tạo phần tử "biến [b]", bạn sẽ sử dụng:
tElem e;
e.typ = ELEM_VARIABLE;
e.str = strdup ("b"); // make sure you free this later
Để tạo phần tử "hằng [7]", bạn sẽ sử dụng:
tElem e;
e.typ = ELEM_CONSTANT;
e.val = 7;
và bạn có thể dễ dàng mở rộng nó để bao gồm float ( float flt
) hoặc r lýs ( struct ratnl {int num; int denom;}
) và các loại khác.
Tiền đề cơ bản là str
và val
không liền kề nhau trong bộ nhớ, chúng thực sự trùng nhau, vì vậy đó là cách để có một cái nhìn khác nhau trên cùng một khối bộ nhớ, được minh họa ở đây, trong đó cấu trúc dựa trên vị trí bộ nhớ 0x1010
và cả số nguyên và con trỏ 4 byte:
+-----------+
0x1010 | |
0x1011 | typ |
0x1012 | |
0x1013 | |
+-----+-----+
0x1014 | | |
0x1015 | str | val |
0x1016 | | |
0x1017 | | |
+-----+-----+
Nếu nó chỉ trong một cấu trúc, nó sẽ trông như thế này:
+-------+
0x1010 | |
0x1011 | typ |
0x1012 | |
0x1013 | |
+-------+
0x1014 | |
0x1015 | str |
0x1016 | |
0x1017 | |
+-------+
0x1018 | |
0x1019 | val |
0x101A | |
0x101B | |
+-------+
make sure you free this later
bình luận được loại bỏ khỏi các yếu tố liên tục?
Tôi muốn nói rằng nó giúp sử dụng lại bộ nhớ có thể được sử dụng theo những cách khác nhau dễ dàng hơn, tức là tiết kiệm bộ nhớ. Ví dụ: bạn muốn thực hiện một số cấu trúc "biến thể" có thể lưu một chuỗi ngắn cũng như một số:
struct variant {
int type;
double number;
char *string;
};
Trong hệ thống 32 bit, điều này sẽ dẫn đến ít nhất 96 bit hoặc 12 byte được sử dụng cho mỗi phiên bản của variant
.
Sử dụng liên kết, bạn có thể giảm kích thước xuống còn 64 bit hoặc 8 byte:
struct variant {
int type;
union {
double number;
char *string;
} value;
};
Bạn có thể tiết kiệm nhiều hơn nếu bạn muốn thêm nhiều loại biến khác nhau, v.v. Điều đó có thể đúng, rằng bạn có thể thực hiện những điều tương tự khi tạo một con trỏ trống - nhưng liên kết làm cho nó dễ tiếp cận hơn cũng như gõ an toàn Khoản tiết kiệm như vậy nghe có vẻ không lớn, nhưng bạn đang tiết kiệm một phần ba bộ nhớ được sử dụng cho tất cả các phiên bản của cấu trúc này.
Thật khó để nghĩ đến một dịp cụ thể khi bạn cần loại cấu trúc linh hoạt này, có lẽ trong một giao thức tin nhắn nơi bạn sẽ gửi các kích cỡ tin nhắn khác nhau, nhưng thậm chí sau đó có thể có những lựa chọn thay thế thân thiện hơn và tốt hơn cho lập trình viên.
Các hiệp hội hơi giống các loại biến thể trong các ngôn ngữ khác - họ chỉ có thể giữ một thứ tại một thời điểm, nhưng thứ đó có thể là int, float, v.v. tùy thuộc vào cách bạn khai báo.
Ví dụ:
typedef union MyUnion MYUNION;
union MyUnion
{
int MyInt;
float MyFloat;
};
MyUnion sẽ chỉ chứa một int HOẶC một float, tùy thuộc vào cái mà bạn thiết lập gần đây nhất . Vì vậy, làm điều này:
MYUNION u;
u.MyInt = 10;
bây giờ bạn giữ một int bằng 10;
u.MyFloat = 1.0;
bây giờ bạn giữ một số float bằng 1.0. Nó không còn giữ một int. Rõ ràng bây giờ nếu bạn thử và thực hiện printf ("MyInt =% d", u.MyInt); thì có lẽ bạn sẽ gặp lỗi, mặc dù tôi không chắc về hành vi cụ thể.
Kích thước của liên minh được quyết định bởi kích thước của trường lớn nhất của nó, trong trường hợp này là float.
sizeof(int) == sizeof(float)
( == 32
) thường.
Nhiều câu trả lời trong số này liên quan đến việc đúc từ loại này sang loại khác. Tôi nhận được nhiều sự sử dụng nhất từ các công đoàn có cùng loại với họ (nghĩa là khi phân tích luồng dữ liệu nối tiếp). Chúng cho phép phân tích / xây dựng một gói đóng khung trở nên tầm thường.
typedef union
{
UINT8 buffer[PACKET_SIZE]; // Where the packet size is large enough for
// the entire set of fields (including the payload)
struct
{
UINT8 size;
UINT8 cmd;
UINT8 payload[PAYLOAD_SIZE];
UINT8 crc;
} fields;
}PACKET_T;
// This should be called every time a new byte of data is ready
// and point to the packet's buffer:
// packet_builder(packet.buffer, new_data);
void packet_builder(UINT8* buffer, UINT8 data)
{
static UINT8 received_bytes = 0;
// All range checking etc removed for brevity
buffer[received_bytes] = data;
received_bytes++;
// Using the struc only way adds lots of logic that relates "byte 0" to size
// "byte 1" to cmd, etc...
}
void packet_handler(PACKET_T* packet)
{
// Process the fields in a readable manner
if(packet->fields.size > TOO_BIG)
{
// handle error...
}
if(packet->fields.cmd == CMD_X)
{
// do stuff..
}
}
Chỉnh sửa Nhận xét về endianness và struct padding là hợp lệ, và mối quan tâm lớn. Tôi đã sử dụng phần thân mã này gần như hoàn toàn trong phần mềm nhúng, hầu hết trong số đó tôi có quyền kiểm soát cả hai đầu của đường ống.
Công đoàn là tuyệt vời. Một cách sử dụng thông minh của các công đoàn mà tôi đã thấy là sử dụng chúng khi xác định một sự kiện. Ví dụ: bạn có thể quyết định rằng một sự kiện là 32 bit.
Bây giờ, trong 32 bit đó, bạn có thể muốn chỉ định 8 bit đầu tiên như một định danh của người gửi sự kiện ... Đôi khi bạn xử lý toàn bộ sự kiện, đôi khi bạn mổ xẻ nó và so sánh các thành phần của nó. công đoàn cho bạn sự linh hoạt để làm cả hai.
sự kiện công đoàn { sự kiện dài không dấu unsign char eventParts [4]; };
Tôi đã sử dụng union khi tôi đang mã hóa cho các thiết bị nhúng. Tôi có C int dài 16 bit. Và tôi cần lấy 8 bit cao hơn và 8 bit thấp hơn khi tôi cần đọc từ / lưu trữ vào EEPROM. Vì vậy, tôi đã sử dụng cách này:
union data {
int data;
struct {
unsigned char higher;
unsigned char lower;
} parts;
};
Nó không yêu cầu dịch chuyển để mã dễ đọc hơn.
Mặt khác, tôi thấy một số mã stl C ++ cũ đã sử dụng union cho phân bổ stl. Nếu bạn quan tâm, bạn có thể đọc mã nguồn sgi stl . Đây là một phần của nó:
union _Obj {
union _Obj* _M_free_list_link;
char _M_client_data[1]; /* The client sees this. */
};
struct
xung quanh higher
/ lower
? Ngay bây giờ cả hai chỉ trỏ đến byte đầu tiên.
Hãy xem điều này: Xử lý lệnh bộ đệm X.25
Một trong nhiều lệnh X.25 có thể được nhận vào bộ đệm và được xử lý tại chỗ bằng cách sử dụng UNION của tất cả các cấu trúc có thể.
Trong các phiên bản đầu của C, tất cả các khai báo cấu trúc sẽ chia sẻ một tập hợp các trường chung. Được:
struct x {int x_mode; int q; float x_f};
struct y {int y_mode; int q; int y_l};
struct z {int z_mode; char name[20];};
một trình biên dịch về cơ bản sẽ tạo ra một bảng các kích thước của cấu trúc (và có thể là sự sắp xếp) và một bảng riêng biệt về tên, loại và các thành phần của cấu trúc. Trình biên dịch đã không theo dõi các thành viên thuộc về cấu trúc nào và sẽ cho phép hai cấu trúc có một thành viên có cùng tên chỉ khi loại và phần bù khớp nhau (như với thành viên q
của struct x
và struct y
). Nếu p là một con trỏ tới bất kỳ loại cấu trúc nào, p-> q sẽ thêm phần bù của "q" vào con trỏ p và tìm nạp "int" từ địa chỉ kết quả.
Với các ngữ nghĩa trên, có thể viết một hàm có thể thực hiện một số thao tác hữu ích trên nhiều loại cấu trúc có thể hoán đổi cho nhau, miễn là tất cả các trường được sử dụng bởi hàm được xếp cùng với các trường hữu ích trong các cấu trúc được đề cập. Đây là một tính năng hữu ích và việc thay đổi C để xác thực các thành viên được sử dụng để truy cập cấu trúc theo các loại cấu trúc được đề cập có nghĩa là sẽ mất nó khi không có phương tiện có cấu trúc có thể chứa nhiều trường được đặt tên tại cùng một địa chỉ. Việc thêm các loại "union" vào C đã giúp lấp đầy khoảng trống đó (mặc dù không, IMHO, cũng như nó nên có).
Một phần thiết yếu của khả năng của công đoàn để lấp đầy khoảng trống đó là việc một con trỏ cho thành viên công đoàn có thể được chuyển đổi thành một con trỏ thành bất kỳ liên minh nào có chứa thành viên đó và một con trỏ cho bất kỳ liên minh nào có thể được chuyển đổi thành một con trỏ cho bất kỳ thành viên nào. Trong khi chỉ số Standard C89 không rõ ràng nói rằng đúc một T*
trực tiếp đến một U*
tương đương với đúc nó vào một con trỏ đến bất kỳ loại công đoàn có chứa cả T
và U
, và sau đó đúc kết đến U*
, hành vi không có định nghĩa của dãy dàn diễn viên sau này sẽ bị ảnh hưởng bởi loại kết hợp được sử dụng và Tiêu chuẩn không chỉ định bất kỳ ngữ nghĩa trái ngược nào cho việc truyền trực tiếp từ T
đến U
. Hơn nữa, trong trường hợp một hàm nhận được một con trỏ không rõ nguồn gốc, hành vi viết một đối tượng thông qua T*
, chuyển đổiT*
đến a U*
, và sau đó đọc đối tượng qua U*
sẽ tương đương với việc viết một liên kết thông qua thành viên của kiểu T
và đọc theo kiểu U
, sẽ được định nghĩa chuẩn trong một vài trường hợp (ví dụ: khi truy cập các thành viên Chuỗi ban đầu chung) và Xác định thực hiện (thay vì hơn Không xác định) cho phần còn lại. Mặc dù rất hiếm khi các chương trình khai thác các bảo đảm CIS với các đối tượng thực tế thuộc loại liên minh, nhưng việc khai thác các đối tượng không rõ nguồn gốc phải hành xử giống như con trỏ đối với các thành viên công đoàn và có các bảo đảm hành vi liên quan.
foo
là phần int
bù 8, thì anyPointer->foo = 1234;
có nghĩa là "lấy địa chỉ trong anyPulum, thay thế nó bằng 8 byte và thực hiện lưu trữ số nguyên của giá trị 1234 đến địa chỉ kết quả. Trình biên dịch sẽ không cần biết hoặc quan tâm liệu có anyPointer
xác định được không bất kỳ loại cấu trúc nào đã foo
được liệt kê trong số các thành viên của nó.
anyPointer
xác nhận với một thành viên struct, thì trình biên dịch sẽ kiểm tra các điều kiện này to have a member with the same name only if the type and offset matched
của bài viết của bạn như thế nào?
p->foo
sẽ phụ thuộc vào loại và độ lệch của foo
. Về cơ bản, p->foo
là tốc ký cho *(typeOfFoo*)((unsigned char*)p + offsetOfFoo)
. Đối với câu hỏi sau của bạn, khi trình biên dịch gặp định nghĩa thành viên cấu trúc, nó yêu cầu không có thành viên nào có tên đó tồn tại hoặc thành viên có tên đó có cùng loại và bù; Tôi đoán rằng điều đó sẽ xảy ra nếu một định nghĩa thành viên cấu trúc không phù hợp tồn tại, nhưng tôi không biết nó xử lý lỗi như thế nào.
Một ví dụ đơn giản và rất hữu ích, là ....
Hãy tưởng tượng:
bạn có uint32_t array[2]
và muốn truy cập Byte thứ 3 và thứ 4 của chuỗi Byte. bạn có thể làm *((uint16_t*) &array[1])
. Nhưng điều này đáng buồn phá vỡ các quy tắc răng cưa nghiêm ngặt!
Nhưng trình biên dịch đã biết cho phép bạn thực hiện các thao tác sau:
union un
{
uint16_t array16[4];
uint32_t array32[2];
}
về mặt kỹ thuật điều này vẫn là vi phạm các quy tắc. nhưng tất cả các tiêu chuẩn được biết đều hỗ trợ việc sử dụng này.