Mục đích của các hiệp hội trong C và C ++


254

Tôi đã sử dụng công đoàn sớm hơn một cách thoải mái; hôm nay tôi đã hoảng hốt khi đọc bài đăng này và biết rằng mã này

union ARGB
{
    uint32_t colour;

    struct componentsTag
    {
        uint8_t b;
        uint8_t g;
        uint8_t r;
        uint8_t a;
    } components;

} pixel;

pixel.colour = 0xff040201;  // ARGB::colour is the active member from now on

// somewhere down the line, without any edit to pixel

if(pixel.components.a)      // accessing the non-active member ARGB::components

thực sự là hành vi không xác định Tôi đang đọc từ một thành viên của hiệp hội khác với hành vi được viết gần đây để dẫn đến hành vi không xác định. Nếu đây không phải là mục đích sử dụng của công đoàn, thì đó là gì? Ai đó có thể vui lòng giải thích nó công phu?

Cập nhật:

Tôi muốn làm rõ một vài điều trong nhận thức muộn màng.

  • Câu trả lời cho câu hỏi không giống với C và C ++; đứa trẻ không biết gì của tôi đã gắn thẻ nó là cả C và C ++.
  • Sau khi quét qua tiêu chuẩn của C ++ 11, tôi không thể kết luận rằng nó gọi truy cập / kiểm tra một thành viên công đoàn không hoạt động là không xác định / không xác định / xác định thực hiện. Tất cả những gì tôi có thể tìm thấy là §9,5 / 1:

    Nếu một liên kết bố cục tiêu chuẩn chứa một số cấu trúc bố cục tiêu chuẩn có chung một chuỗi ban đầu chung và nếu một đối tượng của loại kết hợp bố cục tiêu chuẩn này chứa một trong các cấu trúc bố cục tiêu chuẩn, thì được phép kiểm tra trình tự ban đầu chung của bất kỳ của các thành viên cấu trúc tiêu chuẩn bố trí. §9.2 / 19: Hai cấu trúc bố cục tiêu chuẩn chia sẻ một chuỗi ban đầu chung nếu các thành viên tương ứng có các loại tương thích bố cục và không thành viên nào là trường bit hoặc cả hai đều là trường bit có cùng độ rộng cho một hoặc nhiều chuỗi ban đầu các thành viên.

  • Trong khi ở C, ( C99 TC3 - DR 283 trở đi), việc làm như vậy là hợp pháp ( cảm ơn Pascal Cuoq vì đã đưa ra điều này). Tuy nhiên, cố gắng thực hiện nó vẫn có thể dẫn đến hành vi không xác định , nếu giá trị đọc xảy ra không hợp lệ (được gọi là "biểu diễn bẫy") cho loại được đọc qua. Mặt khác, giá trị đọc được thực hiện được xác định.
  • C89 / 90 gọi điều này theo hành vi không xác định (Phụ lục J) và cuốn sách của K & R nói rằng việc triển khai được xác định. Trích dẫn từ K & R:

    Đây là mục đích của một liên minh - một biến duy nhất có thể nắm giữ một cách hợp pháp bất kỳ một trong một số loại. [...] Miễn là việc sử dụng phù hợp: loại được truy xuất phải là loại được lưu trữ gần đây nhất. Trách nhiệm của người lập trình là theo dõi loại nào hiện đang được lưu trữ trong một liên minh; kết quả phụ thuộc vào việc thực hiện nếu một cái gì đó được lưu trữ dưới dạng một loại và được trích xuất dưới dạng khác.

  • Trích xuất từ ​​TC ++ PL của Stroustrup (nhấn mạnh của tôi)

    Việc sử dụng các công đoàn có thể cần thiết cho sự tương thích của dữ liệu [...] đôi khi bị sử dụng sai cho "chuyển đổi loại ".

Trên hết, câu hỏi này (có tiêu đề vẫn không thay đổi kể từ câu hỏi của tôi) được đặt ra với mục đích tìm hiểu mục đích của các công đoàn VÀ không dựa trên những gì tiêu chuẩn cho phép Eg Sử dụng quyền thừa kế để sử dụng lại mã, tất nhiên, được cho phép bởi tiêu chuẩn C ++, nhưng đó không phải là mục đích hay mục đích ban đầu của việc giới thiệu tính kế thừa như một tính năng ngôn ngữ C ++ . Đây là lý do câu trả lời của Andrey tiếp tục vẫn là câu trả lời được chấp nhận.


11
Nói một cách đơn giản, trình biên dịch được phép chèn phần đệm giữa các phần tử trong một cấu trúc. Do đó, b, g, r,acó thể không liền kề nhau, và do đó không khớp với bố cục của a uint32_t. Điều này ngoài các vấn đề Endianess mà những người khác đã chỉ ra.
Thomas Matthews

8
Đây chính xác là lý do tại sao bạn không nên gắn thẻ câu hỏi C và C ++. Các câu trả lời là khác nhau, nhưng vì người trả lời thậm chí không cho biết họ đang trả lời về thẻ nào (thậm chí họ có biết không?), Bạn nhận được rác.
Pascal Cuoq

5
@downvoter Cảm ơn bạn đã không giải thích, tôi hiểu rằng bạn muốn tôi hiểu một cách kỳ diệu sự
kìm kẹp

1
Liên quan đến ý định ban đầu là có liên minh , hãy nhớ rằng các hiệp hội C sau ngày C tiêu chuẩn vài năm. Nhìn nhanh vào Unix V7 cho thấy một vài loại chuyển đổi thông qua các hiệp hội.
ninjalj

3
scouring C++11's standard I couldn't conclusively say that it calls out accessing/inspecting a non-active union member is undefined [...] All I could find was §9.5/1...có thật không? bạn trích dẫn một ghi chú ngoại lệ , không phải là điểm chính ngay đầu đoạn : "Trong một liên minh, nhiều nhất một trong số các thành viên dữ liệu không tĩnh có thể hoạt động bất cứ lúc nào, nghĩa là giá trị của nhiều nhất một các thành viên dữ liệu không tĩnh có thể được lưu trữ trong một liên minh bất cứ lúc nào. " - và xuống p4: "Nói chung, người ta phải sử dụng các lệnh gọi hàm hủy rõ ràng và các toán tử vị trí mới để thay đổi thành viên tích cực của liên minh "
underscore_d

Câu trả lời:


407

Mục đích của các công đoàn là khá rõ ràng, nhưng vì một số lý do mọi người bỏ lỡ nó khá thường xuyên.

Mục đích của liên minh là để tiết kiệm bộ nhớ bằng cách sử dụng cùng một vùng bộ nhớ để lưu trữ các đối tượng khác nhau vào các thời điểm khác nhau. Đó là nó.

Nó giống như một căn phòng trong một khách sạn. Những người khác nhau sống trong đó trong khoảng thời gian không chồng chéo. Những người này không bao giờ gặp nhau và thường không biết gì về nhau. Bằng cách quản lý đúng cách việc chia sẻ thời gian của các phòng (nghĩa là đảm bảo những người khác nhau không được chỉ định vào một phòng cùng một lúc), một khách sạn tương đối nhỏ có thể cung cấp chỗ ở cho một số lượng người tương đối lớn, đó là những khách sạn là cho.

Đó chính xác là những gì công đoàn làm. Nếu bạn biết rằng một số đối tượng trong chương trình của bạn giữ các giá trị với vòng đời giá trị không chồng lấp, thì bạn có thể "hợp nhất" các đối tượng này thành một liên kết và do đó tiết kiệm bộ nhớ. Giống như một phòng khách sạn có nhiều nhất một người thuê "hoạt động" tại mỗi thời điểm, một công đoàn có nhiều nhất một thành viên "hoạt động" tại mỗi thời điểm của chương trình. Chỉ có thể đọc thành viên "hoạt động". Bằng cách viết vào thành viên khác, bạn chuyển trạng thái "hoạt động" sang thành viên khác.

Vì một số lý do, mục đích ban đầu này của công đoàn đã "bị ghi đè" với một thứ hoàn toàn khác: viết một thành viên của công đoàn và sau đó kiểm tra nó thông qua một thành viên khác. Loại giải thích lại bộ nhớ này (còn gọi là "loại pucky") không phải là cách sử dụng hợp lệ của các công đoàn. Nó thường dẫn đến hành vi không xác định được mô tả là tạo ra hành vi được xác định thực hiện trong C89 / 90.

EDIT: Sử dụng các công đoàn cho các mục đích loại bỏ (ví dụ viết một thành viên và sau đó đọc một thành viên khác) đã được đưa ra một định nghĩa chi tiết hơn trong một trong các Chương trình kỹ thuật theo tiêu chuẩn C99 (xem DR # 257DR # 283 ). Tuy nhiên, hãy nhớ rằng chính thức điều này không bảo vệ bạn khỏi hành vi không xác định bằng cách cố gắng đọc một đại diện bẫy.


37
+1 vì đã được xây dựng công phu, đưa ra một ví dụ thực tế đơn giản và nói về di sản của các công đoàn!
huyền thoại2k

6
Vấn đề tôi gặp phải với câu trả lời này là hầu hết các hệ điều hành tôi đã thấy đều có các tệp tiêu đề thực hiện chính xác điều này. Ví dụ, tôi đã thấy nó trong các phiên bản cũ (trước 64 bit) <time.h>trên cả Windows và Unix. Loại bỏ nó là "không hợp lệ" và "không xác định" là không thực sự đủ nếu tôi được gọi để hiểu mã hoạt động theo cách chính xác này.
TED

31
@AndreyTTHER Chưa bao giờ là hợp pháp khi sử dụng các công đoàn để đánh cắp kiểu cho đến khi gần đây, rất khó khăn: 2004 không phải là rất gần đây, đặc biệt khi xem xét rằng đó chỉ là C99 mà ban đầu được sử dụng một cách vụng về Trong thực tế, loại hình mặc dù các công đoàn là hợp pháp trong C89, hợp pháp trong C11 và tất cả đều hợp pháp trong C99 mặc dù phải đến năm 2004, ủy ban mới sửa chữa từ ngữ không chính xác và phát hành TC3 sau đó. open-std.org/jtc1/sc22/wg14/www/docs/dr_283.htm
Pascal Cuoq

6
@ legends2k Ngôn ngữ lập trình được xác định theo tiêu chuẩn. Bản sửa đổi kỹ thuật 3 của tiêu chuẩn C99 rõ ràng cho phép đánh máy kiểu trong chú thích 82 của nó, mà tôi mời bạn tự đọc. Đây không phải là TV nơi các ngôi sao nhạc rock được phỏng vấn và bày tỏ ý kiến ​​của họ về biến đổi khí hậu. Ý kiến ​​của Stroustrup không ảnh hưởng đến những gì tiêu chuẩn C nói.
Pascal Cuoq

6
@ legends2k " Tôi biết rằng bất kỳ ý kiến ​​cá nhân nào cũng không quan trọng và chỉ có tiêu chuẩn " Ý kiến ​​của người viết trình biên dịch quan trọng hơn nhiều so với đặc tả "ngôn ngữ (cực kỳ kém)".
tò mò

38

Bạn có thể sử dụng các công đoàn để tạo các cấu trúc như sau, trong đó có một trường cho chúng ta biết thành phần nào của liên minh thực sự được sử dụng:

struct VAROBJECT
{
    enum o_t { Int, Double, String } objectType;

    union
    {
        int intValue;
        double dblValue;
        char *strValue;
    } value;
} object;

Tôi hoàn toàn đồng ý, mà không đi vào hỗn loạn hành vi không xác định, có lẽ đây là hành vi dự định tốt nhất của các công đoàn tôi có thể nghĩ ra; nhưng sẽ không lãng phí không gian khi tôi chỉ sử dụng, nói inthoặc char*cho 10 mục của đối tượng []; trong trường hợp nào, tôi thực sự có thể khai báo các cấu trúc riêng biệt cho từng loại dữ liệu thay vì VAROB DỰ ÁN? Nó sẽ không làm giảm sự lộn xộn và sử dụng không gian ít hơn?
huyền thoại2k

3
truyền thuyết: Trong một số trường hợp, bạn chỉ đơn giản là không thể làm điều đó. Bạn sử dụng một cái gì đó như VAROB DỰ ÁN trong C trong cùng trường hợp khi bạn sử dụng Object trong Java.
Erich Kitzmueller

Cấu trúc dữ liệu của các công đoàn được gắn thẻ dường như là một cách sử dụng hợp pháp duy nhất của các công đoàn, như bạn giải thích.
huyền thoại2k

Cũng đưa ra một ví dụ về cách sử dụng các giá trị.
Ciro Santilli 郝海东 冠状 病 事件

1
@CiroSantilli 心 心 Một phần của một ví dụ từ C ++ Primer , có thể giúp ích. Wandbox.org/permlink/cFSrXyG02vOSdBk2
Rick

34

Hành vi không được xác định từ quan điểm ngôn ngữ. Hãy xem xét rằng các nền tảng khác nhau có thể có các ràng buộc khác nhau trong căn chỉnh bộ nhớ và tuổi thọ. Mã trong một endian lớn so với một máy endian nhỏ sẽ cập nhật các giá trị trong cấu trúc khác nhau. Việc sửa hành vi trong ngôn ngữ sẽ yêu cầu tất cả các cài đặt sử dụng cùng một giới hạn (và các ràng buộc căn chỉnh bộ nhớ ...) hạn chế sử dụng.

Nếu bạn đang sử dụng C ++ (bạn đang sử dụng hai thẻ) và bạn thực sự quan tâm đến tính di động, thì bạn chỉ cần sử dụng struct và cung cấp một setter lấy uint32_tvà đặt các trường một cách thích hợp thông qua các hoạt động của bitmask. Điều tương tự có thể được thực hiện trong C với một hàm.

Chỉnh sửa : Tôi đã mong đợi AProgrammer viết ra một câu trả lời để bỏ phiếu và đóng câu trả lời này. Như một số ý kiến ​​đã chỉ ra, endianness được xử lý trong các phần khác của tiêu chuẩn bằng cách để mỗi lần thực hiện quyết định phải làm gì, và căn chỉnh và phần đệm cũng có thể được xử lý khác nhau. Bây giờ, các quy tắc răng cưa nghiêm ngặt mà AProgrammer ngầm ám chỉ là một điểm quan trọng ở đây. Trình biên dịch được phép đưa ra các giả định về việc sửa đổi (hoặc thiếu sửa đổi) các biến. Trong trường hợp kết hợp, trình biên dịch có thể sắp xếp lại các hướng dẫn và di chuyển đọc của từng thành phần màu qua ghi vào biến màu.


+1 để trả lời rõ ràng và đơn giản! Tôi đồng ý, về tính di động, phương pháp bạn đã đưa ra trong đoạn thứ 2 rất tốt; nhưng tôi có thể sử dụng cách tôi đưa ra trong câu hỏi không, nếu mã của tôi được gắn với một kiến ​​trúc duy nhất (trả giá của tính bảo vệ), vì nó tiết kiệm 4 byte cho mỗi giá trị pixel và tiết kiệm thời gian khi chạy chức năng đó ?
huyền thoại2k

Vấn đề về cuối không buộc tiêu chuẩn phải khai báo đó là hành vi không xác định - reinterpret_cast có chính xác các vấn đề cuối cùng, nhưng có hành vi được xác định thực hiện.
JoeG

1
@ legends2k, vấn đề là trình tối ưu hóa có thể cho rằng uint32_t không được sửa đổi bằng cách viết vào uint8_t và do đó bạn nhận được giá trị sai khi sử dụng tối ưu hóa giả định đó ... @Joe, hành vi không xác định xuất hiện ngay khi bạn truy cập vào con trỏ (tôi biết, có một số ngoại lệ).
AProgrammer

1
@ legends2k / AProgrammer: Kết quả của reinterpret_cast được xác định. Sử dụng con trỏ trả về không dẫn đến hành vi không xác định, chỉ trong hành vi được xác định thực hiện. Nói cách khác, hành vi phải nhất quán và được xác định, nhưng nó không khả chuyển.
JoeG

1
@ legends2k: bất kỳ trình tối ưu hóa tốt nào cũng sẽ nhận ra các hoạt động bitwise chọn toàn bộ byte và tạo mã để đọc / ghi byte, giống như liên kết nhưng được xác định rõ (và di động). ví dụ: uint8_t getRed () const {trả về màu & 0x000000FF; } void setRed (uint8_t r) {color = (color & ~ 0x000000FF) | r; }
Ben Voigt

22

Việc sử dụng phổ biến nhất của uniontôi thường xuyên đi qua là răng cưa .

Hãy xem xét những điều sau đây:

union Vector3f
{
  struct{ float x,y,z ; } ;
  float elts[3];
}

Cái này làm gì Nó cho phép truy cập gọn gàng, gọn gàng của Vector3f vec;các thành viên bằng một trong hai tên:

vec.x=vec.y=vec.z=1.f ;

hoặc bằng cách truy cập số nguyên vào mảng

for( int i = 0 ; i < 3 ; i++ )
  vec.elts[i]=1.f;

Trong một số trường hợp, truy cập theo tên là điều rõ ràng nhất bạn có thể làm. Trong các trường hợp khác, đặc biệt là khi trục được chọn theo chương trình, việc dễ dàng hơn là truy cập trục theo chỉ số bằng số - 0 cho x, 1 cho y và 2 cho z.


3
Điều này cũng được gọi type-punninglà cũng được đề cập trong câu hỏi. Ngoài ra ví dụ trong câu hỏi cho thấy một ví dụ tương tự.
huyền thoại2k

4
Đó không phải là loại mánh khóe. Trong ví dụ của tôi các loại khớp , vì vậy không có "chơi chữ", nó chỉ đơn thuần là răng cưa.
bobobobo

3
Có, nhưng vẫn, từ quan điểm tuyệt đối về tiêu chuẩn ngôn ngữ, thành viên được viết và đọc từ là khác nhau, không được xác định như đã đề cập trong câu hỏi.
huyền thoại2k

3
Tôi hy vọng rằng một tiêu chuẩn trong tương lai sẽ khắc phục trường hợp cụ thể này được cho phép theo quy tắc "kế tiếp ban đầu chung". Tuy nhiên, mảng không tham gia vào quy tắc đó theo từ ngữ hiện tại.
Ben Voigt

3
@cquilguy: Rõ ràng không có yêu cầu rằng các thành viên cấu trúc được đặt mà không có phần đệm tùy ý. Nếu kiểm tra mã cho vị trí thành viên cấu trúc hoặc kích thước cấu trúc, mã sẽ hoạt động nếu việc truy cập được thực hiện trực tiếp thông qua liên minh, nhưng việc đọc Tiêu chuẩn nghiêm ngặt sẽ chỉ ra rằng việc lấy địa chỉ của liên minh hoặc thành viên cấu trúc sẽ mang lại một con trỏ không thể sử dụng như một con trỏ của kiểu riêng của nó, nhưng trước tiên phải được chuyển đổi trở lại thành một con trỏ thành kiểu kèm theo hoặc kiểu ký tự. Bất kỳ trình biên dịch có thể hoạt động từ xa nào cũng sẽ mở rộng ngôn ngữ bằng cách làm cho nhiều thứ hoạt động hơn ...
supercat

10

Như bạn nói, đây là hành vi không xác định nghiêm ngặt, mặc dù nó sẽ "hoạt động" trên nhiều nền tảng. Lý do thực sự của việc sử dụng công đoàn là để tạo các hồ sơ biến thể.

union A {
   int i;
   double d;
};

A a[10];    // records in "a" can be either ints or doubles 
a[0].i = 42;
a[1].d = 1.23;

Tất nhiên, bạn cũng cần một số loại phân biệt đối xử để nói những gì biến thể thực sự chứa. Và lưu ý rằng trong các hiệp hội C ++ không được sử dụng nhiều vì chúng chỉ có thể chứa các loại POD - hiệu quả là các loại không có hàm tạo và hàm hủy.


Bạn đã sử dụng nó như vậy (như trong câu hỏi) ?? :)
huyền thoại2k

Đó là một chút phạm vi, nhưng tôi không chấp nhận "hồ sơ biến thể". Đó là, tôi chắc chắn rằng họ đã nghĩ đến, nhưng nếu họ là ưu tiên tại sao không cung cấp cho họ? "Cung cấp khối xây dựng vì nó cũng có thể hữu ích để xây dựng những thứ khác" dường như có khả năng trực giác hơn. Đặc biệt được cung cấp ít nhất một ứng dụng mà có lẽ trong tâm trí - các thanh ghi I / O được ánh xạ bộ nhớ, trong đó các thanh ghi đầu vào và đầu ra (trong khi chồng chéo) là các thực thể riêng biệt với tên riêng, các loại, v.v.
Steve314 22/2/2016

@ Stev314 Nếu đó là mục đích sử dụng mà họ có trong đầu, họ có thể khiến nó không phải là hành vi không xác định.

@Neil: +1 cho người đầu tiên nói về việc sử dụng thực tế mà không cần nhấn hành vi không xác định. Tôi đoán rằng họ có thể đã thực hiện nó được xác định giống như các hoạt động truy quét kiểu khác (reinterpret_cast, v.v.). Nhưng như tôi đã hỏi, bạn đã sử dụng nó để đánh máy chưa?
huyền thoại2k

@Neil - ví dụ về thanh ghi ánh xạ bộ nhớ không được xác định, endian / etc thông thường sang một bên và đưa ra một cờ "dễ bay hơi". Viết vào một địa chỉ trong mô hình này không tham chiếu cùng một thanh ghi như đọc cùng một địa chỉ. Do đó, không có vấn đề "bạn đang đọc lại gì" vì bạn không đọc lại - bất kỳ đầu ra nào bạn đã viết cho địa chỉ đó, khi bạn đọc bạn chỉ đọc một đầu vào độc lập. Vấn đề duy nhất là đảm bảo bạn đọc phía đầu vào của liên minh và viết phía đầu ra. Đã phổ biến trong các công cụ nhúng - có lẽ vẫn còn.
Steve314

8

Trong C, đó là một cách hay để thực hiện một cái gì đó giống như một biến thể.

enum possibleTypes{
  eInt,
  eDouble,
  eChar
}


struct Value{

    union Value {
      int iVal_;
      double dval;
      char cVal;
    } value_;
    possibleTypes discriminator_;
} 

switch(val.discriminator_)
{
  case eInt: val.value_.iVal_; break;

Trong thời gian của bộ nhớ litlle, cấu trúc này sử dụng ít bộ nhớ hơn một cấu trúc có tất cả các thành viên.

Nhân tiện C cung cấp

    typedef struct {
      unsigned int mantissa_low:32;      //mantissa
      unsigned int mantissa_high:20;
      unsigned int exponent:11;         //exponent
      unsigned int sign:1;
    } realVal;

để truy cập các giá trị bit.


Mặc dù cả hai ví dụ của bạn đều được xác định hoàn hảo trong tiêu chuẩn; nhưng, này, sử dụng các trường bit chắc chắn đã bắn mã không thể truy cập được, phải không?
huyền thoại2k

Không, không. Theo như tôi biết nó được hỗ trợ rộng rãi.
Totonga

1
Hỗ trợ trình biên dịch không dịch sang xách tay. Sách C : C (do đó C ++) không đảm bảo thứ tự các trường trong các từ máy, vì vậy nếu bạn sử dụng chúng cho lý do sau, chương trình của bạn sẽ không chỉ không thể di động, nó cũng phụ thuộc vào trình biên dịch.
huyền thoại2k

5

Mặc dù đây là hành vi không được xác định nghiêm ngặt, nhưng trong thực tế, nó sẽ hoạt động với hầu hết mọi trình biên dịch. Đó là một mô hình được sử dụng rộng rãi đến mức bất kỳ trình biên dịch tự tôn nào cũng cần phải làm "điều đúng đắn" trong những trường hợp như thế này. Nó chắc chắn được ưu tiên hơn so với kiểu đánh dấu, có thể tạo mã bị hỏng với một số trình biên dịch.


2
Không có vấn đề về endian sao? Một sửa chữa tương đối dễ dàng so với "không xác định", nhưng đáng để tính đến một số dự án nếu có.
Steve314

5

Trong C ++, Boost Variant triển khai phiên bản an toàn của liên minh, được thiết kế để ngăn chặn hành vi không xác định càng nhiều càng tốt.

Hiệu suất của nó giống hệt với enum + unioncấu trúc (ngăn xếp được phân bổ quá, v.v.) nhưng nó sử dụng danh sách mẫu các loại thay vì enum:)


5

Hành vi có thể không được xác định, nhưng điều đó chỉ có nghĩa là không có "tiêu chuẩn". Tất cả các trình biên dịch phong nha cung cấp #pragmas để kiểm soát việc đóng gói và căn chỉnh, nhưng có thể có các giá trị mặc định khác nhau. Mặc định cũng sẽ thay đổi tùy thuộc vào cài đặt tối ưu hóa được sử dụng.

Ngoài ra, công đoàn không chỉ để tiết kiệm không gian. Họ có thể giúp trình biên dịch hiện đại với loại pucky. Nếu bạn reinterpret_cast<>mọi thứ, trình biên dịch không thể đưa ra các giả định về những gì bạn đang làm. Nó có thể phải vứt bỏ những gì nó biết về loại của bạn và bắt đầu lại (buộc phải ghi lại vào bộ nhớ, điều này rất không hiệu quả trong những ngày này so với tốc độ xung nhịp của CPU).


4

Về mặt kỹ thuật, nó không được xác định, nhưng trong thực tế, hầu hết các trình biên dịch đều xử lý nó giống hệt như sử dụng một loại reinterpret_casttừ loại này sang loại khác, kết quả của việc thực hiện được xác định. Tôi sẽ không mất ngủ vì mã hiện tại của bạn.


" Reinterpret_cast từ loại này sang loại khác, kết quả của việc triển khai được xác định. " Không, không phải vậy. Việc triển khai không phải xác định nó và hầu hết không định nghĩa nó. Ngoài ra, hành vi được xác định cho phép của việc truyền một số giá trị ngẫu nhiên cho một con trỏ là gì?
tò mò

4

Để biết thêm một ví dụ về việc sử dụng thực tế của các hiệp hội, khung CORBA tuần tự hóa các đối tượng bằng cách sử dụng phương pháp hợp nhất được gắn thẻ. Tất cả các lớp do người dùng định nghĩa là thành viên của một liên minh (rất lớn) và một định danh số nguyên cho người sắp xếp biết cách diễn giải liên minh.


4

Những người khác đã đề cập đến sự khác biệt về kiến ​​trúc (ít - endian lớn).

Tôi đọc được vấn đề là do bộ nhớ cho các biến được chia sẻ, sau đó bằng cách viết cho một, các biến khác thay đổi và tùy thuộc vào loại của chúng, giá trị có thể là vô nghĩa.

ví dụ. nghiệp đoàn {float f; int i; } x;

Viết thư cho xi sẽ là vô nghĩa nếu sau đó bạn đọc từ xf - trừ khi đó là những gì bạn dự định để xem xét các thành phần dấu, số mũ hoặc mantissa của float.

Tôi nghĩ đó cũng là một vấn đề liên kết: Nếu một số biến phải được căn chỉnh từ thì bạn có thể không nhận được kết quả mong đợi.

ví dụ. nghiệp đoàn {char c [4]; int i; } x;

Nếu, theo giả thuyết, trên một số máy, một char phải được căn chỉnh từ thì c [0] và c [1] sẽ chia sẻ lưu trữ với i nhưng không phải c [2] và c [3].


Một byte phải được căn chỉnh từ? Điều đó không có ý nghĩa. Một byte không có yêu cầu căn chỉnh, theo định nghĩa.
tò mò

Vâng, tôi có lẽ nên sử dụng một ví dụ tốt hơn. Cảm ơn.
philcolbourn

@cquilguy: Có nhiều trường hợp người ta có thể muốn có các mảng byte được căn chỉnh từ. Nếu một cái có nhiều mảng, ví dụ 1024 byte và thường muốn sao chép cái này sang cái khác, việc chúng được căn chỉnh từ có thể trên nhiều hệ thống tăng gấp đôi tốc độ memcpy()từ cái này sang cái khác. Một số hệ thống có thể sắp xếp một cách đặc biệt các char[]phân bổ xảy ra bên ngoài các cấu trúc / công đoàn vì lý do đó và các lý do khác. Trong ví dụ còn tồn tại, giả định isẽ chồng lấp tất cả các yếu tố của c[]là không di động, nhưng đó là vì không có gì đảm bảo điều đó sizeof(int)==4.
supercat

4

Trong ngôn ngữ C như đã được ghi nhận vào năm 1974, tất cả các thành viên cấu trúc đã chia sẻ một không gian tên chung và ý nghĩa của "ptr-> thành viên" được định nghĩa là thêm sự dịch chuyển của thành viên vào "ptr" và truy cập địa chỉ kết quả bằng cách sử dụng loại của thành viên. Thiết kế này cho phép sử dụng cùng một ptr với các tên thành viên được lấy từ các định nghĩa cấu trúc khác nhau nhưng có cùng độ lệch; lập trình viên đã sử dụng khả năng đó cho nhiều mục đích khác nhau.

Khi các thành viên cấu trúc được gán không gian tên riêng của họ, không thể khai báo hai thành viên cấu trúc có cùng độ dịch chuyển. Việc thêm các liên kết vào ngôn ngữ giúp có thể đạt được cùng một ngữ nghĩa đã có trong các phiên bản trước đó của ngôn ngữ (mặc dù việc không thể có tên được xuất sang ngữ cảnh kèm theo vẫn có thể phải sử dụng find / thay thế để thay thế thành viên foo-> vào foo-> type1.member). Điều quan trọng không phải là nhiều đến nỗi những người thêm công đoàn có ý định sử dụng mục tiêu cụ thể, mà là họ cung cấp một phương tiện mà các lập trình viên đã dựa vào ngữ nghĩa trước đó, dù với mục đích gì , vẫn có thể đạt được cùng một ngữ nghĩa ngay cả khi họ phải sử dụng một cú pháp khác nhau để làm điều đó.


Đánh giá cao bài học lịch sử, tuy nhiên với tiêu chuẩn xác định và chẳng hạn như không xác định, đó không phải là trường hợp trong thời đại đã qua mà cuốn sách K & R là "tiêu chuẩn" duy nhất, người ta phải chắc chắn không sử dụng nó cho mục đích nào và vào đất UB.
huyền thoại2k

2
@ legends2k: Khi Tiêu chuẩn được viết, phần lớn các triển khai C đã đối xử với các công đoàn theo cùng một cách, và cách đối xử như vậy rất hữu ích. Tuy nhiên, một số ít thì không, và các tác giả của Tiêu chuẩn đã miễn cưỡng coi thương hiệu của bất kỳ triển khai hiện có nào là "không tuân thủ". Thay vào đó, họ nhận ra rằng nếu những người triển khai không cần Tiêu chuẩn để bảo họ làm gì đó (bằng chứng là họ đã làm điều đó ), thì việc không xác định hoặc không xác định sẽ chỉ giữ nguyên hiện trạng . Quan niệm rằng nó sẽ làm cho mọi thứ ít được xác định hơn so với trước khi Tiêu chuẩn được viết ...
supercat

2
... Có vẻ như một sự đổi mới gần đây hơn nhiều. Điều đặc biệt đáng buồn ở tất cả những điều này là nếu các nhà văn trình biên dịch nhắm vào các ứng dụng cao cấp đã tìm ra cách thêm các chỉ thị tối ưu hóa hữu ích vào ngôn ngữ mà hầu hết các trình biên dịch triển khai trong những năm 1990, thay vì các tính năng và đảm bảo được hỗ trợ bởi "chỉ" "90% triển khai, kết quả sẽ là một ngôn ngữ có thể hoạt động tốt hơn và đáng tin cậy hơn so với siêu hiện đại C.
supercat

2

Bạn có thể sử dụng aa union vì hai lý do chính:

  1. Một cách thuận tiện để truy cập cùng một dữ liệu theo các cách khác nhau, như trong ví dụ của bạn
  2. Một cách để tiết kiệm không gian khi có các thành viên dữ liệu khác nhau trong đó chỉ có một người có thể 'hoạt động'

1 Thực sự là một hack theo kiểu C để cắt ngắn mã viết trên cơ sở bạn biết cách hoạt động của kiến ​​trúc bộ nhớ của hệ thống đích. Như đã nói, bạn thường có thể thoát khỏi nó nếu bạn không thực sự nhắm mục tiêu nhiều nền tảng khác nhau. Tôi tin rằng một số trình biên dịch có thể cho phép bạn sử dụng các chỉ thị đóng gói (tôi biết họ thực hiện trên các cấu trúc)?

Một ví dụ điển hình về 2. có thể được tìm thấy trong loại VariANT được sử dụng rộng rãi trong COM.


2

Như những người khác đã đề cập, các công đoàn kết hợp với liệt kê và được gói thành các cấu trúc có thể được sử dụng để thực hiện các công đoàn được gắn thẻ. Một cách sử dụng thực tế là triển khai Rust Result<T, E>, ban đầu được triển khai bằng cách sử dụng thuần túy enum(Rust có thể chứa dữ liệu bổ sung trong các biến thể liệt kê). Đây là một ví dụ về C ++:

template <typename T, typename E> struct Result {
    public:
    enum class Success : uint8_t { Ok, Err };
    Result(T val) {
        m_success = Success::Ok;
        m_value.ok = val;
    }
    Result(E val) {
        m_success = Success::Err;
        m_value.err = val;
    }
    inline bool operator==(const Result& other) {
        return other.m_success == this->m_success;
    }
    inline bool operator!=(const Result& other) {
        return other.m_success != this->m_success;
    }
    inline T expect(const char* errorMsg) {
        if (m_success == Success::Err) throw errorMsg;
        else return m_value.ok;
    }
    inline bool is_ok() {
        return m_success == Success::Ok;
    }
    inline bool is_err() {
        return m_success == Success::Err;
    }
    inline const T* ok() {
        if (is_ok()) return m_value.ok;
        else return nullptr;
    }
    inline const T* err() {
        if (is_err()) return m_value.err;
        else return nullptr;
    }

    // Other methods from https://doc.rust-lang.org/std/result/enum.Result.html

    private:
    Success m_success;
    union _val_t { T ok; E err; } m_value;
}
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.