Tại sao một hàm không có tham số (so với định nghĩa hàm thực tế) biên dịch?


388

Tôi vừa bắt gặp mã C của ai đó mà tôi bối rối không biết tại sao nó lại được biên dịch. Có hai điểm tôi không hiểu.

Đầu tiên, nguyên mẫu hàm không có tham số so với định nghĩa hàm thực tế. Thứ hai, tham số trong định nghĩa hàm không có kiểu.

#include <stdio.h>

int func();

int func(param)
{
    return param;
}

int main()
{
    int bla = func(10);    
    printf("%d", bla);
}

Tại sao điều này làm việc? Tôi đã thử nghiệm nó trong một vài trình biên dịch, và nó hoạt động tốt.


77
Đó là K & R C. Chúng tôi đã viết mã như thế này vào những năm 1980 trước khi có các nguyên mẫu đầy đủ chức năng.
hughdbrown

6
gcc không cảnh báo -Wstrict-prototypescho cả int func()int main(): xc: 3: cảnh báo: khai báo hàm không phải là nguyên mẫu. Bạn nên khai báo main()như main(void)là tốt.
Jens

3
@Jens Tại sao bạn chỉnh sửa câu hỏi? Bạn dường như đã bỏ lỡ điểm ...
dlras2

1
Tôi chỉ làm cho ẩn int rõ ràng. Làm thế nào mà bỏ lỡ điểm? Tôi tin rằng vấn đề là tại sao int func();tương thích với int func(arglist) { ... }.
Jens

3
@MatsPeterson Điều này là sai. C99 5.1.2.2.1 hoàn toàn mâu thuẫn với yêu cầu của bạn và tuyên bố rằng không có phiên bản đối số int main(void).
Jens

Câu trả lời:


271

Tất cả các câu trả lời khác đều đúng, nhưng chỉ để hoàn thành

Một hàm được khai báo theo cách sau:

  return-type function-name(parameter-list,...) { body... }

Kiểu trả vềkiểu biến mà hàm trả về. Đây không thể là kiểu mảng hoặc kiểu hàm. Nếu không được đưa ra, thì int được giả định .

Tên hàm là tên của hàm.

danh sách tham số là danh sách các tham số mà hàm lấy bằng dấu phẩy. Nếu không có tham số nào được đưa ra, thì hàm không lấy bất kỳ và nên được xác định bằng một tập ngoặc đơn trống hoặc với từ khóa void. Nếu không có loại biến nào đứng trước một biến trong danh sách paramater, thì int được giả sử . Mảng và hàm không được truyền cho hàm, nhưng được tự động chuyển thành con trỏ. Nếu danh sách được kết thúc bằng dấu chấm lửng (, ...), thì không có số lượng tham số đã đặt. Lưu ý: tiêu đề stdarg.h có thể được sử dụng để truy cập các đối số khi sử dụng dấu chấm lửng.

Và một lần nữa vì lợi ích của sự hoàn chỉnh. Từ đặc tả C11 6: 11: 6 (trang: 179)

Việc sử dụng các bộ khai báo hàm với dấu ngoặc rỗng (không phải là bộ khai báo kiểu tham số định dạng nguyên mẫu) là một tính năng lỗi thời .


2
"Nếu không có loại biến nào đứng trước một biến trong danh sách tham số, thì int được giả sử." Tôi thấy điều này trong liên kết do bạn cung cấp, nhưng tôi không thể tìm thấy nó trong bất kỳ c89, c99 tiêu chuẩn nào ... Bạn có thể cung cấp một nguồn khác không?
godaygo

"Nếu không có tham số nào được đưa ra, thì hàm không lấy bất kỳ và nên được xác định bằng một dấu ngoặc đơn trống" nghe có vẻ mâu thuẫn với câu trả lời của Tony The Lion nhưng tôi chỉ biết rằng câu trả lời của Tony The Lion là chính xác.
jakun

160

Trong C func()có nghĩa là bạn có thể vượt qua bất kỳ số lượng đối số. Nếu bạn không muốn đối số thì bạn phải khai báo là func(void). Loại bạn đang chuyển đến chức năng của mình, nếu không được chỉ định mặc định int.


3
Trên thực tế không có loại mặc định nào cho int. Trong thực tế, tất cả các đối số mặc định là int (ngay cả kiểu trả về). Hoàn toàn ổn khi gọi func(42,0x42);(trong đó tất cả các cuộc gọi phải sử dụng mẫu hai đối số).
Jens

1
Trong C, khá phổ biến đối với các loại không xác định để mặc định thành int như ví dụ khi bạn khai báo một biến: unsign x; Loại x là biến nào? Nó chỉ ra int
cắn của nó

4
Tôi muốn nói rằng int ngầm phổ biến trong thời xa xưa. Nó chắc chắn không còn nữa và trong C99, nó đã bị xóa khỏi C.
Jens

2
Có thể đáng lưu ý rằng câu trả lời này áp dụng cho các nguyên mẫu hàm với danh sách tham số trống, nhưng không phải là định nghĩa hàm với danh sách tham số trống.
tự kỷ

@mnemonicflow đó không phải là "loại không xác định mặc định thành int"; unsignedchỉ là một tên khác cho cùng loại với unsigned int.
Hobbs

58

int func();là một tuyên bố chức năng lỗi thời từ những ngày không có tiêu chuẩn C, tức là thời của K & R C (trước năm 1989, năm tiêu chuẩn "ANSI C" đầu tiên được công bố).

Hãy nhớ rằng không có nguyên mẫu trong K & R C và từ khóa voidchưa được phát minh. Tất cả những gì bạn có thể làm là nói với trình biên dịch về kiểu trả về của hàm. Danh sách tham số trống trong K & R C có nghĩa là "số lượng đối số không xác định nhưng cố định". Cố định có nghĩa là bạn phải gọi hàm với cùng một số args mỗi lần (như trái ngược với một variadic chức năng như printf, nơi số lượng và loại có thể khác nhau cho mỗi cuộc gọi).

Nhiều trình biên dịch sẽ chẩn đoán cấu trúc này; đặc biệt gcc -Wstrict-prototypessẽ cho bạn biết "khai báo hàm không phải là nguyên mẫu", được phát hiện ra, bởi vì nó trông giống như một nguyên mẫu (đặc biệt là nếu bạn bị đầu độc bởi C ++!), nhưng không phải. Đó là một khai báo kiểu trả về kiểu K & R C cũ.

Quy tắc ngón tay cái: Không bao giờ để trống một khai báo danh sách tham số trống, sử dụng int func(void)để được cụ thể. Điều này biến khai báo kiểu trả về K & R thành nguyên mẫu C89 thích hợp. Trình biên dịch hạnh phúc, nhà phát triển hạnh phúc, người kiểm tra tĩnh hạnh phúc. Tuy nhiên, những người bị đánh lừa bởi ^ W ^ Wfond của C ++ có thể co rúm lại, bởi vì họ cần phải nhập thêm ký tự khi họ cố gắng thực hiện các kỹ năng ngoại ngữ của mình :-)


1
Xin đừng liên quan điều này với C ++. Điều thông thường là danh sách đối số trống có nghĩa là không có tham số và mọi người trên thế giới không quan tâm đến việc xử lý các lỗi do những kẻ K & R gây ra khoảng 40 năm trước. Nhưng các học giả của ủy ban tiếp tục kéo theo các lựa chọn tương tự - vào C99, C11.
pfalcon

1
@pfalcon Ý thức thông thường khá chủ quan. Điều thông thường đối với một người là sự điên rồ rõ ràng đối với người khác. Các lập trình viên C chuyên nghiệp biết rằng danh sách tham số trống cho biết số lượng đối số không xác định nhưng cố định. Thay đổi đó có thể sẽ phá vỡ nhiều triển khai. Các ủy ban đổ lỗi cho việc tránh những thay đổi im lặng đang sủa sai cây, IMHO.
Jens

53
  • Danh sách tham số trống có nghĩa là "bất kỳ đối số", vì vậy định nghĩa không sai.
  • Loại thiếu được giả định là int.

Tôi sẽ xem xét bất kỳ bản dựng nào vượt qua điều này là thiếu mức cảnh báo / lỗi được định cấu hình, tuy nhiên, không có điểm nào trong việc cho phép mã thực tế này.


30

Đó là khai báo và định nghĩa hàm kiểu K & R. Từ tiêu chuẩn C99 (ISO / IEC 9899: TC3)

Mục 6.7.5.3 Công cụ khai báo chức năng (bao gồm cả nguyên mẫu)

Một danh sách định danh chỉ khai báo các định danh của các tham số của hàm. Một danh sách trống trong một bộ khai báo hàm là một phần của định nghĩa của hàm đó xác định rằng hàm không có tham số. Danh sách trống trong một bộ khai báo hàm không phải là một phần của định nghĩa của hàm đó xác định rằng không có thông tin nào về số lượng hoặc loại tham số được cung cấp. (Nếu cả hai loại chức năng là "kiểu cũ", các loại tham số không được so sánh.)

Mục 6.11.6 Bộ khai báo hàm

Việc sử dụng các bộ khai báo hàm với dấu ngoặc rỗng (không phải là bộ khai báo kiểu tham số định dạng nguyên mẫu) là một tính năng lỗi thời.

Mục 6.11.7 Định nghĩa hàm

Việc sử dụng các định nghĩa hàm với các danh sách khai báo và định danh tham số riêng biệt (không phải là kiểu khai báo tham số định dạng nguyên mẫu và định danh) là một tính năng lỗi thời.

Những phong cách cũ có nghĩa là K & R phong cách

Thí dụ:

Tờ khai: int old_style();

Định nghĩa:

int old_style(a, b)
    int a; 
    int b;
{
     /* something to do */
}

16

C giả sử intnếu không có kiểu nào được đưa ra trên kiểu trả về hàm và danh sách tham số . Chỉ cho quy tắc này sau những điều kỳ lạ là có thể.

Một định nghĩa hàm trông như thế này.

int func(int param) { /* body */}

Nếu nó là một nguyên mẫu bạn viết

int func(int param);

Trong nguyên mẫu, bạn chỉ có thể chỉ định loại tham số. Tên tham số không bắt buộc. Vì thế

int func(int);

Ngoài ra nếu bạn không chỉ định loại tham số nhưng tên intđược giả sử là loại.

int func(param);

Nếu bạn đi xa hơn, làm việc sau đây quá.

func();

Trình biên dịch giả định int func()khi bạn viết func(). Nhưng không đặt func()bên trong một cơ thể chức năng. Đó sẽ là một cuộc gọi chức năng


3
Một danh sách tham số trống không liên quan đến kiểu int ẩn. int func()không phải là một hình thức ngầm của int func(int).
John Bartholomew

11

Như @Krishnabhadra đã nêu, tất cả các phản hồi trước đây từ những người dùng khác, đều có cách giải thích chính xác và tôi chỉ muốn phân tích chi tiết hơn về một số điểm.

Trong Old-C như trong ANSI-C, " tham số chính thức chưa được xử lý ", lấy phần nhỏ của thanh ghi công việc hoặc khả năng độ sâu chỉ dẫn (thanh ghi bóng hoặc chu trình tích lũy lệnh), trong MPU 8 bit, sẽ là int16, trong 16 bit MPU và như vậy sẽ là một int16, v.v., trong trường hợp kiến ​​trúc 64 bit có thể chọn biên dịch các tùy chọn như: -m32.

Mặc dù có vẻ đơn giản hơn khi thực hiện ở mức cao, Để vượt qua nhiều tham số, công việc của lập trình viên trong bước loại dữ liệu điều khiển dimencion, trở nên đòi hỏi khắt khe hơn.

Trong các trường hợp khác, đối với một số kiến ​​trúc bộ vi xử lý, trình biên dịch ANSI đã tùy chỉnh, tận dụng một số tính năng cũ này để tối ưu hóa việc sử dụng mã, buộc vị trí của các "tham số chính thức chưa được xử lý" này hoạt động trong hoặc ngoài thanh ghi công việc, hôm nay bạn có được gần như giống nhau với việc sử dụng "dễ bay hơi" và "đăng ký".

Nhưng cần lưu ý rằng các trình biên dịch hiện đại nhất, không phân biệt giữa hai loại khai báo tham số.

Ví dụ về một trình biên dịch với gcc trong linux:

C chính

main2.c

main3.c  
Trong mọi trường hợp, câu lệnh của nguyên mẫu cục bộ không được sử dụng, bởi vì không có cuộc gọi nào mà không có tham số tham chiếu đến nguyên mẫu này sẽ được gửi lại. Nếu bạn sử dụng hệ thống với "tham số chính thức chưa được kiểm tra", đối với cuộc gọi bên ngoài, hãy tiến hành tạo kiểu dữ liệu nguyên mẫu khai báo.

Như thế này:

int myfunc(int param);

5
Tôi đã gặp khó khăn để tối ưu hóa hình ảnh cho rằng kích thước tính bằng byte của hình ảnh, là mức tối thiểu có thể. Tôi nghĩ rằng các hình ảnh có thể có được một cái nhìn nhanh chóng về sự thiếu khác biệt trong mã được tạo. Tôi đã thử nghiệm mã, tạo tài liệu thực về những gì nó tuyên bố và không chỉ lý thuyết hóa về vấn đề. nhưng không phải tất cả đều nhìn thế giới theo cùng một cách Tôi vô cùng xin lỗi vì việc tôi cố gắng cung cấp thêm thông tin cho cộng đồng đã làm phiền bạn rất nhiều.
RTOSkit

1
Tôi khá chắc chắn rằng loại trả về không xác định luôn luôn int, thường là kích thước thanh ghi công việc hoặc 16 bit, tùy theo loại nào nhỏ hơn.
supercat

@supercat +1 Khấu trừ hoàn hảo, bạn đã đúng! Đây chính xác là những gì tôi thảo luận ở trên, một trình biên dịch được thiết kế theo mặc định cho một kiến ​​trúc xác định, luôn phản ánh kích thước của thanh ghi công việc của CPU / MPU được đề cập, Vì vậy, trong một môi trường nhúng, có nhiều chiến lược gõ lại khác nhau trên một HĐH thời gian thực, vào lớp trình biên dịch (stdint.h) hoặc thành lớp khả chuyển, giúp di động cùng một HĐH trong nhiều cấu trúc khác và thu được bằng cách căn chỉnh các loại cụ thể của CPU / MPU (int, long, long long, v.v.) với các loại hệ thống chung u8, u16, u32.
RTOSkit

@supercat Không có các loại điều khiển được cung cấp bởi một hệ điều hành hoặc bởi trình biên dịch cụ thể, bạn cần có một chút suy nghĩ trong thời gian phát triển và, bạn làm cho tất cả các "bài tập chưa được chỉnh sửa" phù hợp với thiết kế ứng dụng của bạn, trước khi có bất ngờ tìm thấy " 16 bit int "chứ không phải" int 32 bit ".
RTOSkit

@RTOSkit: Câu trả lời của bạn cho thấy loại mặc định trên bộ xử lý 8 bit sẽ là Int8. Tôi nhớ lại một vài trình biên dịch C-ish cho kiến ​​trúc thương hiệu PICmicro trong trường hợp đó, nhưng tôi không nghĩ bất cứ điều gì giống với tiêu chuẩn C đã từng cho phép một intloại không có khả năng giữ tất cả các giá trị trong phạm vi - 32767 đến +32767 (không cần lưu ý -32768) và cũng có khả năng giữ tất cả các chargiá trị (có nghĩa là nếu charlà 16 bit, thì nó phải được ký hoặc intphải lớn hơn).
supercat

5

Về loại tham số, đã có câu trả lời đúng ở đây nhưng nếu bạn muốn nghe nó từ trình biên dịch, bạn có thể thử thêm một số cờ (dù sao cờ cũng luôn là một ý tưởng hay).

biên dịch chương trình của bạn bằng cách sử dụng gcc foo.c -Wextratôi nhận được:

foo.c: In function func’:
foo.c:5:5: warning: type of param defaults to int [-Wmissing-parameter-type]

kỳ lạ là -Wextrakhông nắm bắt được điều này vì clang(nó không nhận ra -Wmissing-parameter-typevì một số lý do, có thể vì những điều lịch sử đã đề cập ở trên) nhưng -pedantic:

foo.c:5:10: warning: parameter 'param' was not declared, 
defaulting to type 'int' [-pedantic]
int func(param)
         ^
1 warning generated.

Và đối với vấn đề nguyên mẫu như đã nói ở trên int func()đề cập đến các tham số tùy ý trừ khi bạn xác định chính xác nó như int func(void)sau đó sẽ cung cấp cho bạn các lỗi như mong đợi:

foo.c: In function func’:
foo.c:6:1: error: number of arguments doesnt match prototype
foo.c:3:5: error: prototype declaration
foo.c: In function main’:
foo.c:12:5: error: too many arguments to function func
foo.c:5:5: note: declared here

hoặc clangnhư:

foo.c:5:5: error: conflicting types for 'func'
int func(param)
    ^
foo.c:3:5: note: previous declaration is here
int func(void);
    ^
foo.c:12:20: error: too many arguments to function call, expected 0, have 1
    int bla = func(10);
              ~~~~ ^~
foo.c:3:1: note: 'func' declared here
int func(void);
^
2 errors generated.

3

Nếu khai báo hàm không có tham số tức là trống thì nó đang lấy số lượng đối số không xác định. Nếu bạn muốn làm cho nó không có đối số thì thay đổi nó thành:

int func(void);

1
"Nếu nguyên mẫu hàm không có tham số" Đó không phải là nguyên mẫu hàm, chỉ là khai báo hàm.
effeffe

@effeffe đã sửa. Tôi đã không nhận ra câu hỏi này sẽ được chú ý nhiều;)
PP

3
Không phải "nguyên mẫu hàm" chỉ là một biểu thức thay thế (có thể lỗi thời) cho "khai báo hàm" sao?
Giorgio

@Giorgio IMO, cả hai đều đúng. "Nguyên mẫu hàm" phải khớp với "định nghĩa hàm". Tôi có thể nghĩ effeffe có nghĩa là tuyên bố trong câu hỏi và ý tôi là gì trong câu trả lời của tôi.
PP

@Giorgio không, khai báo hàm được thực hiện bởi giá trị loại trả về, mã định danh và danh sách tham số tùy chọn ; nguyên mẫu hàm là khai báo hàm với danh sách tham số.
effeffe

0

Đây là lý do tại sao tôi thường khuyên mọi người biên dịch mã của họ với:

cc -Wmissing-variable-declarations -Wstrict-variable-declarations -Wold-style-definition

Những lá cờ này thi hành một vài điều:

  • -Wmissing-biến-khai báo: Không thể khai báo hàm không tĩnh mà không lấy nguyên mẫu trước. Điều này làm cho nhiều khả năng một nguyên mẫu trong tệp tiêu đề khớp với định nghĩa thực tế. Ngoài ra, nó bắt buộc bạn thêm từ khóa tĩnh vào các chức năng không cần hiển thị công khai.
  • -Wstrict-biến-khai báo: Nguyên mẫu phải liệt kê đúng các đối số.
  • -Wold-style-định nghĩa: Bản thân định nghĩa hàm cũng phải liệt kê đúng các đối số.

Các cờ này cũng được sử dụng theo mặc định trong rất nhiều dự án Nguồn mở. Ví dụ: FreeBSD có các cờ này được bật khi xây dựng với WARNS = 6 trong Makefile của bạn.

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.