2008-11-04 30 views
23

Tôi đã cố gắng hiểu các quy tắc bí danh nghiêm ngặt khi chúng áp dụng cho con trỏ char.Khi nào char * an toàn cho việc đánh dấu con trỏ nghiêm ngặt?

Here này được nêu:

Người ta luôn luôn coi đó là một char * có thể tham khảo một bí danh của bất kỳ đối tượng.

Ok như vậy trong bối cảnh của mã ổ cắm, tôi có thể làm điều này:

struct SocketMsg 
{ 
    int a; 
    int b; 
}; 

int main(int argc, char** argv) 
{ 
    // Some code... 
    SocketMsg msgToSend; 
    msgToSend.a = 0; 
    msgToSend.b = 1; 
    send(socket, (char*)(&msgToSend), sizeof(msgToSend); 
}; 

Nhưng sau đó có tuyên bố này

Các converse là không đúng sự thật. Đúc một char * vào một con trỏ thuộc bất kỳ kiểu nào khác hơn là char * và dereferencing nó thường vi phạm quy tắc bí danh nghiêm ngặt.

Điều này có nghĩa rằng khi tôi recv một mảng char, tôi không thể diễn giải lại dàn diễn viên cho một struct khi tôi biết cấu trúc của thông điệp:

struct SocketMsgToRecv 
{ 
    int a; 
    int b; 
}; 

int main() 
{ 
    SocketMsgToRecv* pointerToMsg; 
    char msgBuff[100]; 
    ... 
    recv(socket, msgBuff, 100); 
    // Ommiting make sure we have a complete message from the stream 
    // but lets assume msgBuff[0] has a complete msg, and lets interpret the msg 

    // SAFE!?!?!? 
    pointerToMsg = &msgBuff[0]; 

    printf("Got Msg: a: %i, b: %i", pointerToMsg->a, pointerToMsg->b); 
} 

sẽ ví dụ thứ hai này không làm việc vì loại cơ sở là một mảng char và tôi đang đúc nó vào một cấu trúc? Làm thế nào để bạn xử lý tình trạng này trong một thế giới bí danh nghiêm ngặt?

+0

Không phải đoạn mã thứ hai yêu cầu bạn phải viết rõ ràng? Bạn đã bật tất cả cảnh báo chưa? –

Trả lời

6

Chính xác, ví dụ thứ hai là vi phạm quy tắc bí danh nghiêm ngặt, vì vậy nếu bạn biên dịch với cờ -fstrict-aliasing, có khả năng bạn có thể nhận được mã đối tượng không chính xác. Giải pháp hoàn toàn chính xác sẽ là sử dụng liên minh tại đây:

union 
{ 
    SocketMsgToRecv msg; 
    char msgBuff[100]; 
}; 

recv(socket, msgBuff, 100); 

printf("Got Msg: a: %i, b: %i", msg.a, msg.b); 
+1

Điều này có phù hợp với trình biên dịch chuẩn hay chỉ cho phép bạn lấy đi bằng văn bản cho một thành viên và đọc từ một thành viên khác? –

+9

Công đoàn là hoàn toàn không cần thiết. Đơn giản chỉ cần chuyển một con trỏ tới cấu trúc (truyền tới 'char *') tới 'recv'. –

+0

Lưu ý rằng '-fstrict-aliasing' được bật theo mặc định tại' -O2' và cao hơn trong gcc –

7

Re @Adam Rosenfield: Công đoàn sẽ đạt được sự liên kết miễn là nhà cung cấp của char * bắt đầu thực hiện một việc tương tự.

Có thể hữu ích khi đứng lại và tìm hiểu xem điều này là gì.

Cơ sở cho quy tắc bí danh là thực tế các trình biên dịch có thể đặt các giá trị của các loại đơn giản khác nhau trên các ranh giới bộ nhớ khác nhau để cải thiện truy cập và phần cứng đó trong một số trường hợp có thể yêu cầu sự liên kết đó để có thể sử dụng con trỏ. Điều này cũng có thể hiển thị trong các cấu trúc có nhiều yếu tố có kích thước khác nhau. Cấu trúc có thể được bắt đầu trên một ranh giới tốt. Ngoài ra, trình biên dịch vẫn có thể giới thiệu các vết cắn slack ở bên trong của cấu trúc để hoàn thành việc căn chỉnh các phần tử struct yêu cầu nó.

Xem xét các trình biên dịch thường có các tùy chọn để kiểm soát cách xử lý tất cả điều này, hoặc không, bạn có thể thấy rằng có nhiều cách gây bất ngờ. Điều này đặc biệt quan trọng cần lưu ý khi truyền con trỏ tới các cấu trúc (gọi là char * hay không) thành các thư viện được biên dịch để mong đợi các quy ước căn chỉnh khác nhau.

Còn char * thì sao?

Giả định về char * là sizeof (char) == 1 (liên quan đến kích thước của tất cả các dữ liệu khá lớn khác) và con trỏ char * không có bất kỳ yêu cầu căn chỉnh nào. Vì vậy, một char chính hãng * luôn luôn có thể được truyền đi một cách an toàn và sử dụng thành công mà không cần phải quan tâm đến sự liên kết, và nó cho bất kỳ phần tử nào của mảng char [], thực hiện ++ và - trên con trỏ, v.v. (Oddly, void * không hoàn toàn giống nhau.Bây giờ bạn sẽ có thể thấy làm thế nào nếu bạn chuyển một số loại dữ liệu cấu trúc vào một mảng char [] mà nó không được sắp xếp một cách thích hợp, cố gắng đưa trở lại một con trỏ yêu cầu (các) liên kết có thể một vấn đề nghiêm trọng.

Nếu bạn kết hợp một mảng char [] và một cấu trúc, căn chỉnh đòi hỏi nhiều nhất (tức là cấu trúc của cấu trúc) sẽ được trình biên dịch vinh danh. Điều này sẽ làm việc nếu nhà cung cấp và người tiêu dùng đang sử dụng hiệu quả các công đoàn phù hợp để đúc cấu trúc * thành char * và hoạt động trở lại tốt.

Trong trường hợp đó, tôi hy vọng rằng dữ liệu được tạo trong một liên kết tương tự trước khi con trỏ tới nó được chuyển thành char * hoặc nó được chuyển bất kỳ cách nào khác như một mảng các byte sizeof (char). Nó cũng quan trọng để đảm bảo rằng bất kỳ tùy chọn trình biên dịch nào tương thích giữa các thư viện dựa vào và mã của riêng bạn.

+0

là tất cả 3 của 'char',' signed char', 'unsigned char' OK cho răng cưa? và với bất kỳ kết hợp trình độ CV nào không? –

+2

Quy tắc bí danh không liên quan gì đến căn chỉnh. Theo lý do C89, được khai báo toàn cục như 'int i; float * fp; ', mục đích là cho phép các trình biên dịch giữ' i' trong một thanh ghi trên các truy cập tới '* fp'. Ý tưởng là một trình biên dịch không nên bi quan giả sử rằng một ghi vào '* fp' có thể thay đổi' i' * khi nó không có lý do gì để mong đợi * rằng '* fp' sẽ chỉ ra một thứ không phải là' float' *. Tôi không nghĩ rằng quy tắc đã từng có ý định để cho trình biên dịch bỏ qua các trường hợp mà răng cưa là rõ ràng (lấy địa chỉ của một đối tượng nên cung cấp cho một trình biên dịch một đầu mối mạnh ... – supercat

+0

... rằng đối tượng được đề cập sắp được truy cập thông qua con trỏ, và đúc 'int *' thành 'float *' sẽ cung cấp cho trình biên dịch manh mối mạnh mẽ rằng 'int' có thể được sửa đổi thông qua ghi vào' float * ', nhưng gcc không còn cảm thấy bất kỳ nghĩa vụ nào đối với – supercat

Các vấn đề liên quan