Tại sao main không trả về 0 ở đây?


116

Tôi vừa mới đọc

Dự thảo của Ủy ban ISO / IEC 9899: 201x - ngày 12 tháng 4 năm 2011

trong đó tôi tìm thấy theo 5.1.2.2.3 Chấm dứt chương trình

..reaching the } that terminates the main function returns a value of 0. 

nó có nghĩa là nếu bạn không chỉ định bất kỳ câu lệnh return nào trong main()và nếu chương trình chạy thành công, thì tại dấu ngoặc nhọn} của main sẽ trả về 0.

Nhưng trong đoạn mã sau, tôi không chỉ định bất kỳ câu lệnh trả lại nào, nhưng nó không trả về 0

#include<stdio.h>
int sum(int a,int b)
{
return (a + b);
}

int main()
{
    int a=10;
    int b=5;
    int ans;    
    ans=sum(a,b);
    printf("sum is %d",ans);
}

biên dịch

gcc test.c  
./a.out
sum is 15
echo $?
9          // here it should be 0 but it shows 9 why?

69
+1 vì có đủ kiên nhẫn để đọc thông số kỹ thuật .....
Asher

16
gcctự nó (đối với phiên bản 4.6.2) biên dịch một ngôn ngữ rất giống nhưng không hoàn toàn giống C. Nó biên dịch GnuC89 - một ngôn ngữ "lỏng lẻo" dựa trên C89.
pmg

2
Dấu ngoặc đơn trên returncâu lệnh trong sum()là không cần thiết. int main()nên được int main(void).
Keith Thompson

24
Nhầm lẫn! = Lỗi đánh máy. Trên bàn phím của tôi, '0' và 'o' đủ gần để dễ dàng trở thành bàn phím sau. ;-)
111

2
IMHO là một đặc tả khá ngu ngốc, vì nó buộc trình biên dịch quản lý chức năng "chính" theo một cách đặc biệt bằng cách thêm một "return 0" ngầm. Vì vậy, một hàm có tên "main" hoạt động theo một cách hơi khác. Điều gì về kiểm tra thời gian biên dịch ("không có giá trị trả lại" tương tự)?
Giuseppe Guerrini,

Câu trả lời:


141

Quy tắc đó đã được bổ sung trong phiên bản 1999 của tiêu chuẩn C. Trong C90, trạng thái trả về là không xác định.

Bạn có thể kích hoạt nó bằng cách chuyển -std=c99tới gcc.

Như một lưu ý phụ, thú vị là số 9 được trả lại vì đó là sự trở lại của số printfchỉ viết 9 ký tự.


40
Hoặc bạn có thể thêm return 0;trước khi kết thúc }. Nó vô hại và làm cho chương trình của bạn có thể di động đến các trình biên dịch cũ hơn.
Keith Thompson

2
@ Mr.32: quan sát tốt, printf () trả về độ dài của chuỗi nên nó là 9 là "return" của main (không sử dụng -std = c99).
Hicham

1
@cnicutar: Các hàm thường không trả về các giá trị nhỏ trên ngăn xếp — bởi vì nó sẽ liên quan đến một cửa sổ bật lên, một lần đẩy và một bước nhảy hơn là chỉ một bước di chuyển và trả lại — vì vậy, nó gần như chắc chắn là một thanh ghi, eaxcụ thể là trên x86.
Jon Purdy

8
Có, API x86 thường trả về giá trị nguyên giống như giá trị thông qua thanh eaxghi. Xem en.wikipedia.org/wiki/X86_calling_conventions#cdecl để biết thêm thông tin.
Sylvain Defresne

2
Một vài lần tôi đã thấy mã như "int foo (void) {bar ();}", trong đó "return bar ()" được dự định. Mã này hoạt động tốt trên hầu hết các bộ xử lý, mặc dù có lỗi rõ ràng.
ugoren

15

Nó trả về giá trị trả về printflà số ký tự thực sự được in ra.


câu hỏi không nói về những gì được in trong bảng điều khiển khi thực thi chương trình, nó nói về giá trị trả về của chương trình: (bạn có thể lấy nó trong linux bằng lệnh schell: echo $? và trong windows với: echo% errorlevel% )
Hicham

1
@eharvest Mặc dù tôi không biết cách kiểm tra %errorlevel%trong Windows, nhưng có sự khác biệt nào giữa mã thoát và giá trị trả về của maintrong linux không?
Summer_More_More_Tea

1
@eharvest: Câu trả lời không nói về những gì được in trong bảng điều khiển. Nó nói về giá trị trả về printfmà trong trường hợp này là 9, sau đó được "thăng cấp" làm mã thoát khỏi mainbằng cách nào đó khi sử dụng các phiên bản gcc nhất định.
AH

lấy làm tiếc. lỗi của tôi. bạn đúng rồi. tôi đọc câu trả lời này quá nhanh: /
Hicham

1
Lưu ý rằng điều này không được đảm bảo. Trong C89 / C90, trạng thái trả về trong trường hợp này là không xác định ; nó có thể là bất cứ điều gì. Nó xảy ra để trả về 9 vì trình biên dịch không cố gắng trả về bất kỳ thứ gì khác. Các trình biên dịch khác có thể sẽ hoạt động khác.
Keith Thompson,

6

Giá trị trả về từ một hàm thường được lưu trữ trong thanh ghi eax của cpu, vì vậy câu lệnh "return 4;" thường sẽ biên dịch thành

mov eax, 4;
ret;

và trả về x (tùy thuộc vào trình biên dịch của bạn) sẽ giống như sau:

mov eax, [ebp + 4];
ret;

nếu bạn không chỉ định giá trị trả về thì trình biên dịch sẽ vẫn đưa ra "ret" nhưng không thay đổi giá trị của eax. Vì vậy, người gọi sẽ nghĩ rằng những gì đã từng còn lại trong thanh ghi eax trước đây là giá trị trả về. Đối với ví dụ này, nó thường là giá trị trả về printf, nhưng các trình biên dịch khác nhau sẽ tạo ra mã máy khác nhau và sử dụng một số thanh ghi khác nhau.

Đây là cách giải thích đơn giản, các quy ước gọi khác nhau và nền tảng đích sẽ đóng vai trò quan trọng nhưng nó phải đủ thông tin để giải thích những gì đang xảy ra 'đằng sau hậu trường' trong ví dụ của bạn.

Nếu bạn đã có hiểu biết cơ bản về trình hợp dịch, bạn nên so sánh việc tháo rời các trình biên dịch khác nhau. Bạn có thể thấy rằng một số trình biên dịch đang xóa sổ đăng ký eax như một biện pháp bảo vệ.


11
Trình biên dịch có thể làm điều đó. Nó không xác định. Thay vào đó, nó có thể chọn bắn vào đầu bạn bằng hộp mực máy in.
Các cuộc đua ánh sáng trong quỹ đạo

@LightnessRacesinOrbit không xác định là không giống như không thể đoán trước, tức là từ "nếu trình biên dịch thực hiện X, nó sẽ được Tuân thủ tiêu chuẩn" nó không hợp lý theo "trình biên dịch có thể làm X"
Owen

@Owen: Trích dẫn đoạn văn chuẩn xác định điều này.
Các cuộc đua ánh sáng trong quỹ đạo vào

2
@LightnessRacesinOrbit Tôi xin lỗi vì tôi không thực sự theo dõi được. Tôi đoán những gì tôi thực sự muốn nói là tôi nghĩ rằng những thông tin chi tiết ẩn như noggin182 được cung cấp trong câu trả lời này cực kỳ hữu ích cho việc gỡ lỗi. Khi chương trình của bạn tạo ra kết quả không mong muốn, rất nhiều khi bạn không biết chúng đến từ đâu hoặc thậm chí là nơi mã cần tìm, và hiểu rõ về các chi tiết triển khai có thể giúp bạn đi đúng hướng.
Owen

@Owen Tôi chưa bao giờ tuyên bố khác
Lightness Races ở Orbit
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.