2010-05-05 55 views
29

Một số mã cũ mà tôi chỉ đi qua:Tại sao mã này vẫn hoạt động?

MLIST * new_mlist_link() 
{ 
    MLIST *new_link = (MLIST *) malloc(sizeof(MLIST)); 
    new_link->next = NULL; 
    new_link->mapi = NULL; 
    new_link->result = 0; 
} 

này đã được kêu gọi để xây dựng một danh sách liên kết, tuy nhiên tôi nhận thấy không có tuyên bố:

return new_link; 

Mặc dù không có báo cáo kết quả lợi nhuận ở đó, danh sách vẫn được xây dựng đúng cách. Tại sao điều này xảy ra?

Edit: Hệ điều hành: Mandriva 2009 64bit Linux 2.6.24.7-server GCC 4.2.3-6mnb1

Edit: vui ... mã này cũng chạy successfuly trên khoảng 5 cài đặt Linux khác nhau, tất cả các phiên bản/hương vị khác nhau cũng như máy Mac.

+2

Không có câu trả lời có thể còn một số tồn câm may mắn, trừ khi (hoặc thậm chí nếu) bạn cho chúng tôi biết những gì nền tảng. – Potatoswatter

+1

Điều này tạo ra một trường hợp tốt để sử dụng trình kiểm tra mã tĩnh. – semaj

+3

Thú vị là nó chạy trên rất nhiều nền tảng. Nó chỉ có một biến cục bộ, vì vậy nó phải khá chuẩn cho GCC trên Intel để lưu trữ một biến cục bộ duy nhất trong EAX (hoặc tương đương 64 bit của nó), và cũng có thể sử dụng thanh ghi đó cho các giá trị trả về. –

Trả lời

35

Trên Windows 32 bit, hầu hết thời gian, giá trị trả lại từ một hàm được để lại trong thanh ghi EAX. Các thiết lập tương tự được sử dụng trong các hệ điều hành khác, mặc dù tất nhiên nó là trình biên dịch cụ thể. Hàm cụ thể này có lẽ được lưu trữ biến new_link trong cùng một vị trí, vì vậy khi bạn quay trở lại mà không trả về, biến ở vị trí đó được coi là giá trị trả về của người gọi.

Đây không phải là di động và rất nguy hiểm để thực sự làm, nhưng cũng là một trong những điều nhỏ mà làm cho lập trình trong C rất nhiều niềm vui.

+4

+1, với việc bổ sung rằng đó là ABI, không phải là hệ điều hành xác định quy ước gọi điện. – danben

+1

Điều đó giải thích lý do tại sao nó chạy, nhưng tại sao nó được biên dịch là câu hỏi tiếp theo. : P Bạn nghĩ rằng trình biên dịch sẽ kiểm tra một câu lệnh trả về (tôi biết tất cả những gì tôi làm). Dù bằng cách nào, trong khi nó hoạt động, nó chắc chắn có thể được dán nhãn là Bad Thing. – ssube

+1

Tôi nghĩ rằng kiểu mã hóa này phổ biến hơn 30 năm trước khi C trở nên phổ biến. Dự án mà mã này đến từ có lẽ được viết bởi một coder cũ (hoặc chỉ là một thời gian dài trước đây) và đã có cảnh báo trình biên dịch bị vô hiệu hóa để điều này sẽ biên dịch chính xác. –

5

Về cơ bản là may mắn; Rõ ràng, trình biên dịch xảy ra để dính new_link vào cùng một vị trí nó sẽ dính một giá trị trả về.

-1

Có thể trùng hợp ngẫu nhiên:

Không gian cho giá trị trả lại của hàm được phân bổ trước thời hạn. Vì giá trị đó là uninitialized, nó có thể trỏ đến cùng một không gian trên heap như bộ nhớ được cấp phát cho struct.

+1

Uhh, không, các giá trị trả lại thường được trả về trong thanh ghi, hoặc, giả sử, trên ngăn xếp. Nó không phải là một con trỏ ngẫu nhiên được chính xác một cách bí ẩn. Giá trị trả về không được phân bổ. – WhirlWind

+1

Như đã nêu ở trên, các giá trị trả lại được trả về trong thanh ghi nếu chúng phù hợp, như trường hợp của con trỏ. – Joel

+4

@Whirl: Trên một số kiến ​​trúc, không gian được dành riêng trên ngăn xếp cho các giá trị trả về khi một thủ tục được gọi. Trên những người khác, kết quả được trả về trong sổ đăng ký. Nó ** khác **. –

1

Điều này hoạt động theo sự trùng hợp ngẫu nhiên. Bạn không nên dựa vào điều đó.

7

Có thể nó chỉ sử dụng thanh ghi EAX thường lưu trữ giá trị trả về của hàm cuối cùng được gọi. Đây không phải là thực hành tốt cả! Hành vi cho loại sự việc này là không xác định .. Nhưng nó là mát mẻ để xem công việc ;-)

1

Rất có thể là nó sẽ sản xuất rất khó tìm lỗi. Tôi không chắc chắn nơi tôi đọc nó, nhưng tôi nhớ lại rằng nếu bạn quên đặt trong một tuyên bố trở lại, hầu hết các trình biên dịch sẽ mặc định để trả về void.

Dưới đây là một ví dụ nhỏ:

#include <iostream> 

using namespace std; 

int* getVal(); 

int main() 
{ 
     int *v = getVal(); 
     cout << "Value is: " << *v << endl; 
     return 0; 
} 

int* getVal() 
{ 
     // return nothing here 
} 

Đối với tôi, công trình này là tốt. Tuy nhiên, khi tôi chạy nó, tôi nhận được một lỗi phân đoạn. Vì vậy, nó thực sự không xác định. Chỉ vì nó biên dịch, không có nghĩa là nó sẽ hoạt động.

+1

Watson tôi đoán? –

0

Nó hoạt động vì vào những năm 1940 khi ngôn ngữ C được tạo, không có từ khóa return.Nếu bạn nhìn vào "chức năng" của tiêu chuẩn C43 MINSI, nó có này để nói về đề tài này (trong số những thứ khác):

16.4.3b For backwards compatibility the EAX register MUST be used to return 
     the address of the first chunk of memory allocated by malloc. 

</humour>

+4

Không, không phải vậy. Không có tiêu chuẩn C43 MINSI, anh ấy chỉ làm điều này. –

+0

Bây giờ chúng ta đều biết hài hước của James. tại sao lại bỏ phiếu? – tristan

5

Để tránh vấn đề này, sử dụng:

-Wreturn-type:

Warn whenever a function is defined with a return-type that defaults to int. Also warn about any return statement with no return-value in a function whose return-type is not void (falling off the end of the function body is considered returning without a value), and about a return statement with an expression in a function whose return-type is void.

-Werror=return-type để biến ở trên vào một lỗi:

Make the specified warning into an error. The specifier for a warning is appended, for example -Werror=switch turns the warnings controlled by -Wswitch into errors. This switch takes a negative form, to be used to negate -Werror for specific warnings, for example -Wno-error=switch makes -Wswitch warnings not be errors, even when -Werror is in effect. You can use the -fdiagnostics-show-option option to have each controllable warning amended with the option which controls it, to determine what to use with this option.

(từ GCCs warning options)

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