Tại sao chúng ta cần C Liên hiệp?


236

Khi nào nên sử dụng công đoàn? Tại sao chúng ta cần chúng?

Câu trả lời:


251

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 Schỉ là 12 byte, thay vì 28.


nên có uy thay vì uf
Amit Singh Tomar

1
Liệu ví dụ nào có thể chuyển đổi float sang số nguyên hoạt động? Tôi không nghĩ vậy, vì int và float được lưu trữ ở các định dạng khác nhau trong bộ nhớ. Bạn có thể giải thích ví dụ của bạn?
spin_eight

3
@spin_eight: Nó không "chuyển đổi" từ float sang int. Giống như "diễn giải lại biểu diễn nhị phân của một float như thể nó là một int". Đầu ra không phải là 3: ideone.com/MKjwon Tôi không chắc tại sao Adam lại in dưới dạng hex.
endolith

@Adam Rosenfield Tôi thực sự không đủ khả năng chuyển đổi Tôi không nhận được số nguyên trong đầu ra: p
The Beast

2
Tôi cảm thấy từ chối trách nhiệm về hành vi không xác định nên được loại bỏ. Đó là, trên thực tế, hành vi được xác định. Xem chú thích 82 của tiêu chuẩn C99: Nếu thành viên được sử dụng để truy cập nội dung của đối tượng hợp nhất không giống với thành viên cuối cùng được sử dụng để lưu trữ một giá trị trong đối tượng, phần thích hợp của biểu diễn đối tượng của giá trị được diễn giải lại như một đại diện đối tượng trong loại mới như được mô tả trong 6.2.6 (một quá trình đôi khi được gọi là "loại pucky"). Đây có thể là một đại diện bẫy.
Christian Gibbons

135

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;

3
Câu trả lời của bạn ở đây kết hợp với câu trả lời của @Adam Rosenfield ở trên tạo nên cặp bổ sung hoàn hảo: bạn thể hiện bằng cách sử dụng một cấu trúc trong một liên minh và anh ấy chứng minh bằng cách sử dụng một liên kết trong một cấu trúc . Hóa ra tôi cần cả hai cùng một lúc: một cấu trúc trong một liên kết trong một cấu trúc để thực hiện một số đa hình truyền thông điệp ưa thích trong C giữa các luồng trên một hệ thống nhúng và tôi sẽ không nhận ra rằng tôi đã không thấy cả hai câu trả lời của bạn .
Gabriel Staples

1
Tôi đã sai: đó là một liên minh trong một cấu trúc trong một liên kết trong một cấu trúc, được lồng bên trái để viết khi tôi viết điều đó, từ cấp độ bên trong nhất đến cấp độ ngoài cùng. Tôi đã phải thêm một liên minh khác ở cấp độ bên trong nhất để cho phép các giá trị của các loại dữ liệu khác nhau.
Gabriel Staples

64

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;

3
Đây là một ví dụ tuyệt vời! Dưới đây là một ví dụ về cách bạn có thể sử dụng kỹ thuật này trong phần mềm nhúng: edn.com/design/integrated-circuit-design/4394915/ Kẻ
rzetterberg

34

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


33

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ớ.


29

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 structvà 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;
}

Chính xác những gì tôi đang tìm kiếm ! Rất hữu ích để thay thế một số tham số dấu chấm lửng :)
Nicolas Voron

17

Đâ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:

  • biểu tượng [bộ]
  • biến [a]
  • biểu tượng [để]
  • biến [b]
  • biểu tượng [lần]
  • hằng số [7]
  • Biểu tượng[.]

Các yếu tố ngôn ngữ được định nghĩa là #definecá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à strvalkhô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ớ 0x1010và 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 |       |
       +-------+

Nên make sure you free this laterbình luận được loại bỏ khỏi các yếu tố liên tục?
Trevor

Có, @Trevor, mặc dù tôi không thể tin rằng bạn là người đầu tiên nhìn thấy nó trong hơn 4 năm qua :-) Đã sửa và cảm ơn vì điều đó.
paxdiablo

7

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.


5

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.


1
sizeof(int) == sizeof(float)( == 32) thường.
Nick T

1
Đối với bản ghi, việc gán cho float sau đó in int sẽ không gây ra lỗi, vì cả trình biên dịch lẫn môi trường thời gian chạy đều biết giá trị nào là hợp lệ. Tất nhiên, int được in sẽ vô nghĩa đối với hầu hết các mục đích. Nó sẽ chỉ là đại diện bộ nhớ của float, được hiểu là int.
Jerry B

4

Liên hiệp được sử dụng khi bạn muốn mô hình hóa các cấu trúc được xác định bởi phần cứng, thiết bị hoặc giao thức mạng hoặc khi bạn đang tạo một số lượng lớn đối tượng và muốn tiết kiệm không gian. Mặc dù vậy, bạn thực sự không cần đến 95% thời gian, hãy sử dụng mã dễ gỡ lỗi.


4

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.


1
Mã này sẽ không hoạt động (hầu hết các lần) nếu dữ liệu được trao đổi trên 2 nền tảng khác nhau vì những lý do sau: 1) Độ bền có thể khác nhau. 2) Đệm trong các cấu trúc.
Mahori

@Ravi Tôi đồng ý với những lo ngại về tuổi thọ và đệm. Tuy nhiên, cần biết rằng tôi đã sử dụng điều này độc quyền trong các dự án nhúng. Hầu hết trong số đó tôi kiểm soát cả hai đầu của đường ống.
Adam Lewis

1

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];
};

1

Điều gì về VARIANTđiều đó được sử dụng trong giao diện COM? Nó có hai trường - "loại" và liên minh giữ một giá trị thực được xử lý tùy thuộc vào trường "loại".


1

Ở trường, tôi đã sử dụng các công đoàn như thế này:

typedef union
{
  unsigned char color[4];
  int       new_color;
}       u_color;

Tôi đã sử dụng nó để xử lý màu sắc dễ dàng hơn, thay vì sử dụng các toán tử >> và <<, tôi chỉ phải trải qua các chỉ mục khác nhau của mảng char.


1

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.        */
};

1
Bạn sẽ không cần một nhóm structxung quanh higher/ lower? Ngay bây giờ cả hai chỉ trỏ đến byte đầu tiên.
Mario

@Mario ah đúng rồi, tôi chỉ viết nó bằng tay và quên nó đi, cảm ơn
Mu Qiao

1
  • Một tập tin chứa các loại bản ghi khác nhau.
  • Một giao diện mạng chứa các loại yêu cầu khác nhau.

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ể.


bạn có thể vui lòng giải thích cả hai ví dụ này không. Ý tôi là chúng liên quan đến sự hợp nhất như thế nào
Amit Singh Tomar

1

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 qcủa struct xstruct 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ả TU, 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 Tvà đọ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.


bạn có thể đưa ra một ví dụ về điều này: '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 hoán đổi cho nhau'. Làm thế nào có thể được sử dụng nhiều thành viên cấu trúc có cùng tên? Nếu hai sctructures có cùng sự liên kết dữ liệu và do đó, một thành viên có cùng tên và cùng độ lệch như trong ví dụ của bạn, thì từ cấu trúc nào tôi sẽ mang lại dữ liệu thực tế? (giá trị). Hai cấu trúc có cùng sự liên kết và cùng các thành viên, nhưng giá trị khác nhau trên chúng. Bạn có thể vui lòng giải thích nó không
Herdsman

@Herdsman: Trong các phiên bản đầu của C, tên thành viên cấu trúc được gói gọn một loại và phần bù. Hai thành viên của các cấu trúc khác nhau có thể có cùng tên nếu và chỉ khi loại và độ lệch của chúng khớp với nhau. Nếu thành viên struct foolà phần intbù 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ó anyPointerxá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ó.
supercat

Với con trỏ, bạn có thể hủy bỏ mọi địa chỉ bất kể 'nguồn gốc' của con trỏ, điều đó là đúng, nhưng điểm nào của trình biên dịch để giữ các bảng cấu trúc thành viên và tên của chúng (như bạn đã nói trong bài đăng của bạn) nếu tôi có thể tìm nạp dữ liệu với bất kỳ con trỏ chỉ biết địa chỉ của một thành viên trong một cấu trúc cụ thể? Và nếu trình biên dịch không biết liệu trình anyPointerxá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 matchedcủa bài viết của bạn như thế nào?
Herdsman

@Herdsman: Trình biên dịch sẽ giữ danh sách tên của các thành viên cấu trúc vì hành vi chính xác p->foosẽ phụ thuộc vào loại và độ lệch của foo. Về cơ bản, p->foolà 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.
supercat

0

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.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.