Tại sao mã này mặc định trên kiến ​​trúc 64 bit nhưng hoạt động tốt trên 32 bit?


112

Tôi đã xem qua câu đố C sau:

H: Tại sao chương trình sau đây mặc định trên IA-64, nhưng lại hoạt động tốt trên IA-32?

  int main()
  {
      int* p;
      p = (int*)malloc(sizeof(int));
      *p = 10;
      return 0;
  }

Tôi biết rằng kích thước của intmáy 64 bit có thể không bằng kích thước của con trỏ ( intcó thể là 32 bit và con trỏ có thể là 64 bit). Nhưng tôi không chắc điều này liên quan như thế nào đến chương trình trên. Có ý kiến ​​gì không?


50
Nó là một cái gì đó ngớ ngẩn như stdlib.hkhông được bao gồm?
user786653

3
Mã này chạy tốt trên máy 64 bit của tôi. Nó thậm chí biên dịch mà không cần cảnh báo nếu bạn #include stdlib.h(ví malloc)
mpenkov

1
Ôi! @ user786653 đóng đinh bit quan trọng. Với #include <stdlib.h>, nó hoàn toàn được tìm thấy, nhưng đó không phải là câu hỏi.

8
@delnan - tuy nhiên, nó không nhất thiết phải hoạt động như vậy, nó có thể bị lỗi một cách hợp pháp trên một nền tảng sizeof(int) == sizeof(int*), ví dụ, nếu các con trỏ được trả về thông qua một đăng ký khác với ints trong quy ước gọi được sử dụng.
Flexo

7
Trong môi trường C99, trình biên dịch phải cung cấp cho bạn ít nhất một cảnh báo về việc khai báo ngầm định malloc(). GCC nói: warning: incompatible implicit declaration of built-in function 'malloc'quá.
Jonathan Leffler,

Câu trả lời:


130

Việc ép kiểu để int*che giấu thực tế rằng nếu không có #includekiểu trả về thích hợp mallocđược giả định là int. IA-64 xảy ra sự cố sizeof(int) < sizeof(int*)khiến vấn đề này trở nên rõ ràng.

(Cũng lưu ý rằng do hành vi không xác định, nó vẫn có thể không thành công ngay cả trên nền tảng sizeof(int)==sizeof(int*)đúng, ví dụ: nếu quy ước gọi sử dụng các thanh ghi khác nhau để trả về con trỏ chứ không phải số nguyên)

Câu hỏi thường gặp về comp.lang.c có một mục thảo luận về lý do tại sao việc truyền trả lại từ mallockhông bao giờ cần thiết và có khả năng xấu .


5
nếu không có #include thích hợp, tại sao kiểu trả về của malloc được giả định là int?
user7

11
@WTP - đó là lý do chính đáng để luôn sử dụng newtrong C ++ và luôn biên dịch C bằng trình biên dịch C chứ không phải trình biên dịch C ++.
Flexo

6
@ user7 - đó là các quy tắc. Bất kỳ loại trả lại nào được giả định là intnếu nó không được biết
Flexo

2
@vlad - ý tưởng tốt hơn là luôn khai báo các hàm thay vì dựa vào các khai báo ngầm vì chính xác lý do này. (Và không thực hiện trả lại từ malloc)
Flexo

16
@ user7: "chúng tôi có một con trỏ p (kích thước 64) đang trỏ đến 32 bit bộ nhớ" - sai. Địa chỉ của khối được cấp phát bởi malloc được trả về theo quy ước gọi cho a void*. Nhưng mã gọi cho rằng hàm trả về int(vì bạn đã chọn không cho nó biết cách khác), vì vậy nó cố gắng đọc giá trị trả về theo quy ước gọi cho an int. Do đó pkhông không nhất thiết chỉ để cấp phát bộ nhớ. Nó thực sự hoạt động cho IA32 vì an intvà a void*có cùng kích thước và được trả về theo cùng một cách. Trên IA64, bạn nhận được giá trị sai.
Steve Jessop

33

Nhiều khả năng là do bạn không bao gồm tệp tiêu đề cho mallocvà, trong khi trình biên dịch thường cảnh báo bạn về điều này, thực tế là bạn đang truyền giá trị trả về một cách rõ ràng có nghĩa là bạn đang cho nó biết bạn đang làm gì.

Điều đó có nghĩa là trình biên dịch mong đợi một intđược trả về mallocmà từ đó nó chuyển tới một con trỏ. Nếu chúng có kích thước khác nhau, điều đó sẽ khiến bạn đau buồn.

Đây là lý do tại sao bạn không bao giờ truyền malloctrả về trong C. Con void*trỏ trả về sẽ được chuyển đổi ngầm thành một con trỏ đúng loại (trừ khi bạn không bao gồm tiêu đề trong trường hợp đó, nó có thể đã cảnh báo bạn về int- chuyển đổi con trỏ).


xin lỗi vì nghe có vẻ ngây thơ, nhưng tôi luôn giả định rằng malloc trả về một con trỏ void có thể được chuyển thành một kiểu thích hợp. Tôi không phải là một lập trình viên C và do đó sẽ đánh giá cao hơn một chút chi tiết.
user7

5
@ user7: không có #include <stdlib.h> trình biên dịch C sẽ giả định giá trị trả về của malloc là một int.
sashang

4
@ user7: Con trỏ void có thể được ép kiểu, nhưng nó không cần thiết trong C vì void *có thể được chuyển đổi ngầm sang bất kỳ loại con trỏ nào khác. int *p = malloc(sizeof(int))hoạt động nếu nguyên mẫu thích hợp nằm trong phạm vi và không thành công nếu không (vì khi đó kết quả được giả định là như vậy int). Với cast, cả hai sẽ biên dịch và sau này sẽ dẫn đến lỗi khi sizeof(int) != sizeof(void *).

2
@ user7 Nhưng nếu bạn không bao gồm stdlib.h, trình biên dịch không biết mallocvà cũng không phải kiểu trả về của nó. Vì vậy, nó chỉ giả sử intlà mặc định.
Christian Rau

10

Đây là lý do tại sao bạn không bao giờ biên dịch mà không có cảnh báo về việc thiếu nguyên mẫu.

Đây là lý do tại sao bạn không bao giờ truyền trả về malloc trong C.

Diễn viên là cần thiết để tương thích với C ++. Có rất ít lý do (đọc: không có lý do ở đây) để bỏ qua nó.

Không phải lúc nào cũng cần khả năng tương thích với C ++ và trong một vài trường hợp là không thể, nhưng trong hầu hết các trường hợp, nó rất dễ đạt được.


22
Tại sao tôi lại quan tâm liệu mã C của tôi có "tương thích" với C ++ không? Tôi không quan tâm nếu nó tương thích với perl hoặc java hoặc Eiffel hay ...
Stephen Canon

4
Nếu bạn đảm bảo ai đó sẽ không nhìn vào mã C của bạn và bỏ đi, tôi sẽ biên dịch nó bằng trình biên dịch C ++ vì điều đó sẽ hoạt động!
Steven Lu

3
Đó là nguyên nhân của hầu hết các mã C có thể được trivially thực hiện tương thích C ++.
curiousguy
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.