2011-01-08 43 views
10

Trong C, bạn có thể truyền cả hai loại dữ liệu đơn giản như int, float và trỏ tới các loại này. Bây giờ tôi đã giả định rằng nếu bạn muốn chuyển đổi từ một con trỏ thành một loại thành giá trị của một loại khác (ví dụ: từ *float đến int), thứ tự truyền và dereferencing không thành vấn đề. I E. cho biến số float* pf, bạn có (int) *pf == *((int*) pf). Sắp xếp như tính tương đối trong toán học ...Trong C, nếu tôi bỏ & dereference một con trỏ, nó có vấn đề gì tôi làm đầu tiên?

Tuy nhiên, điều này có vẻ không đúng. Tôi đã viết một chương trình thử nghiệm:

#include <stdio.h> 
int main(int argc, char *argv[]){ 
    float f = 3.3; 
    float* pf = &f; 
    int i1 = (int) (*pf); 
    int i2 = *((int*) pf); 
    printf("1: %d, 2: %d\n", i1, i2); 
    return 0; 
} 

và trên hệ thống của tôi đầu ra là

1: 3, 2: 1079194419 

Vì vậy, đúc con trỏ dường như làm việc khác với đúc giá trị.

Tại sao lại như vậy? Tại sao các phiên bản thứ hai không làm những gì tôi nghĩ rằng nó nên?

Và điều này phụ thuộc vào nền tảng hay tôi bằng cách nào đó gọi hành vi không xác định?

Trả lời

11

Sau đây nhận được float tại pf và chuyển đổi nó thành một số nguyên. Việc đúc ở đây là một yêu cầu chuyển đổi phao thành một số nguyên. Trình biên dịch tạo mã để chuyển đổi giá trị float thành một giá trị số nguyên. (Chuyển đổi giá trị float để nguyên là một "bình thường" là điều phải làm.)

int i1 = (int) (*pf); 

Sau đây nói đầu tiên buộc các trình biên dịch để suy nghĩ điểm pf đến một số nguyên (và bỏ qua thực tế là pf là một con trỏ đến một phao) và sau đó nhận được giá trị số nguyên (nhưng nó không phải là một giá trị số nguyên). Đây là một điều kỳ lạ và nguy hiểm để làm. Việc đúc trong trường hợp này sẽ TẮT chuyển đổi thích hợp. Trình biên dịch thực hiện một bản sao đơn giản của các bit ở bộ nhớ (tạo ra thùng rác). (Và cũng có thể có vấn đề liên kết bộ nhớ!)

int i2 = *((int*) pf); 

Trong tuyên bố thứ hai, bạn không phải là "chuyển đổi" con trỏ. Bạn đang nói cho trình biên dịch những gì bộ nhớ trỏ đến (trong ví dụ này, là sai).

Hai câu này đang thực hiện rất những thứ khác nhau!

Hãy nhớ rằng c một số lần sử dụng cùng một cú pháp để mô tả các hoạt động khác nhau.

=============

Lưu ý rằng đôi là loại dấu phẩy động mặc định trong C (thư viện toán thường sử dụng đối số kép).

1

Một int không được đại diện trong bộ nhớ giống như phao. Những gì bạn đang thấy là trình biên dịch nghĩ con trỏ là một int và nó nhìn vào 32 bit của bộ nhớ nghĩ rằng nó sẽ tìm thấy một int khi nó là trong thực tế tìm kiếm một phần không xác định của phao, mà đi ra một số rất số lượng lớn.

4

Nếu trước tiên bạn dereference, cast to int, bạn sẽ nhận được hành vi thông thường (truncation) của phôi từ float sang int. Nếu bạn chuyển sang con trỏ tới int trước, sau đó dereference, hành vi không được xác định theo tiêu chuẩn. Thông thường nó biểu hiện trong việc giải thích bộ nhớ chứa float như int. Xem http://en.wikipedia.org/wiki/IEEE_754-2008 để biết cách hoạt động.

+3

Thông thường nó hiển thị dưới dạng vi phạm "bí danh nghiêm ngặt" dẫn đến việc đọc sai dữ liệu hoặc không có dữ liệu, ít nhất là trên gcc hiện đại. –

+1

Nó không xuất hiện, nó là một vi phạm, và có, gcc thường cảnh báo bạn về nó (nó không bắt tất cả các trường hợp, afaik). Tôi nói nó không được xác định, tôi chỉ giải thích những gì xảy ra trong trường hợp của anh ấy - bạn có thể dễ dàng kiểm tra điều đó, ví dụ: với một đoạn trích nhỏ '[3.3] .pack ("f"). giải nén ("I") => [1079194419] ' – etarion

+0

Chỉ để biết thông tin, chương trình ví dụ của tôi ở trên biên dịch mà không có cảnh báo trong' -Wall', vì vậy có, gcc không bắt tất cả các trường hợp ;-). – sleske

3

Tất nhiên rồi! Casting cho trình biên dịch biết cách xem xét phần nào đó của bộ nhớ. Khi bạn truy cập vào bộ nhớ, nó sẽ cố gắng giải thích dữ liệu ở đó dựa trên cách bạn nói nó nhìn vào nó. Sẽ dễ hiểu hơn khi sử dụng ví dụ sau.

int main() 
{ 
    char A[] = {0, 0, 0, 1 }; 
    int p = *((int*)A); 
    int i = (int)*A; 
    printf("%d %d\n", i, p); 
    return 0; 
} 

Đầu ra trên một máy cuối nhỏ 32 bit sẽ là 0 16777216. Điều này là do (int*)A yêu cầu trình biên dịch xử lý A làm con trỏ thành số nguyên và do đó khi bạn dereference A, nó xem xét 4 byte bắt đầu từ A (as sizeof(int) == 4). Sau khi chiếm các endian-Ness, nội dung của 4 byte để đánh giá 16777216. Mặt khác, *A dereferences A đến lấy 0(int)*A phôi rằng để có được 0.

+0

Cảm ơn ví dụ. BTW, tôi tin rằng thậm chí không có một đảm bảo rằng 'int' là 32 bit, nó có thể là 16bit. Với một 16bit int ví dụ của bạn nên cung cấp cho 256 (= 2^8) trên kiến ​​trúc nhỏ, phải không? – sleske

+0

Đã giả định số nguyên 32 bit. Sẽ cập nhật câu trả lời để làm rõ điều đó. Nếu int là 16 bit (2 byte), chỉ có hai byte (A [0], A [1]) sẽ được sử dụng. Như A [0] = A [1] = 0, giá trị số nguyên sẽ là 0. – 341008

2

Cast, sau đó dereference có nghĩa là "giả vờ tôi chỉ vào điều khác này, và sau đó nhận được thứ khác được cho là được chỉ vào ".

Dereference, sau đó cast có nghĩa là "có được điều tôi đang thực sự chỉ vào, và sau đó chuyển đổi nó sang điều này khác theo các quy tắc thông thường".

"Quy tắc thông thường" có thể thay đổi các bit xung quanh để nhận giá trị của một loại khác thể hiện một cách hợp lý cùng một giá trị. Giả vờ bạn đang chỉ vào một cái gì đó bạn không thực sự chỉ vào không thể.

0

Theo 6.5/7 của tiêu chuẩn, gần như nói, giá trị được lưu trữ phải tương thích với loại hiệu quả hoặc một loại ký tự. Vì vậy, tôi nghĩ rằng tuyên bố int i2 = *((int*) pf) không được xác định rõ.

+0

Câu lệnh gần như chắc chắn ** sai **. – davep

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