Câu hỏi bạn đang hỏi thực sự là hai câu hỏi, không phải một. Hầu hết các câu trả lời cho đến nay đều cố gắng che toàn bộ nội dung bằng một tấm chăn chung chung "đây là kiểu K&R", trong khi trên thực tế, chỉ một phần nhỏ của nó có liên quan gì đến thứ được gọi là kiểu K&R (trừ khi bạn thấy toàn bộ ngôn ngữ C. là "K & R-style" theo cách này hay cách khác :)
Phần đầu tiên là cú pháp lạ được sử dụng trong định nghĩa hàm
int func(p, p2)
void *p;
int p2;
{
return 0;
}
Đây thực sự là một định nghĩa hàm kiểu K & R. Câu trả lời khác đã bao gồm điều này khá tốt. Và thực ra thì chẳng có gì nhiều. Cú pháp không được dùng nữa, nhưng vẫn được hỗ trợ đầy đủ ngay cả trong C99 (ngoại trừ quy tắc "không có int ngầm định" trong C99, nghĩa là trong C99, bạn không thể bỏ qua khai báo của p2
).
Phần thứ hai ít liên quan đến phong cách K&R. Tôi đề cập đến thực tế là hàm có thể được gọi với các đối số "hoán đổi", tức là không có việc kiểm tra kiểu tham số nào diễn ra trong một lệnh gọi như vậy. Điều này rất ít liên quan đến định nghĩa kiểu K & R, nhưng nó liên quan đến mọi thứ liên quan đến chức năng của bạn không có nguyên mẫu. Bạn thấy đấy, trong C khi bạn khai báo một hàm như thế này
int foo();
nó thực sự khai báo một hàm foo
nhận một số tham số không xác định của kiểu không xác định . Bạn có thể gọi nó là
foo(2, 3);
và như
j = foo(p, -3, "hello world");
ans so (bạn có được ý tưởng);
Chỉ lệnh gọi với các đối số thích hợp mới "hoạt động" (nghĩa là các lệnh khác tạo ra hành vi không xác định), nhưng việc đảm bảo tính đúng đắn của nó là hoàn toàn tùy thuộc vào bạn. Trình biên dịch không bắt buộc phải chẩn đoán những cái không chính xác ngay cả khi bằng cách nào đó nó biết một cách kỳ diệu các loại tham số chính xác và tổng số của chúng.
Trên thực tế, hành vi này là một tính năng của ngôn ngữ C. Một điều nguy hiểm, nhưng vẫn là một tính năng. Nó cho phép bạn làm điều gì đó như thế này
void foo(int i);
void bar(char *a, double b);
void baz(void);
int main()
{
void (*fn[])() = { foo, bar, baz };
fn[0](5);
fn[1]("abc", 1.0);
fn[2]();
}
tức là kết hợp các kiểu hàm khác nhau trong một mảng "đa hình" mà không có bất kỳ định dạng nào (mặc dù không thể sử dụng kiểu hàm đa dạng ở đây). Một lần nữa, sự nguy hiểm cố hữu của kỹ thuật này là khá rõ ràng (tôi không nhớ đã bao giờ sử dụng nó, nhưng tôi có thể tưởng tượng nó có thể hữu ích ở đâu), nhưng đó là C.
Cuối cùng, bit liên kết phần thứ hai của câu trả lời với phần đầu tiên. Khi bạn đưa ra định nghĩa hàm kiểu K & R, nó không giới thiệu một nguyên mẫu cho hàm. Đối với kiểu hàm có liên quan, func
định nghĩa của bạn khai báo func
là
int func();
tức là cả kiểu và tổng số tham số đều không được khai báo. Trong bài đăng ban đầu của bạn, bạn nói "... nó có vẻ chỉ định là nó sử dụng bao nhiêu tham số ...". Chính thức mà nói, nó không! Sau khi func
xác định kiểu K & R hai tham số, bạn vẫn có thể gọifunc
là
func(1, 2, 3, 4, "Hi!");
và sẽ không có bất kỳ vi phạm ràng buộc nào trong đó. (Thông thường, một trình biên dịch chất lượng sẽ đưa ra cảnh báo cho bạn).
Ngoài ra, một thực tế đôi khi bị bỏ qua là
int f()
{
return 0;
}
cũng là một định nghĩa chức năng kiểu K&R không giới thiệu nguyên mẫu. Để làm cho nó "hiện đại", bạn phải đặt một cách rõ ràng void
trong danh sách tham số
int f(void)
{
return 0;
}
Cuối cùng, trái với quan niệm phổ biến, cả định nghĩa hàm kiểu K & R và khai báo hàm không nguyên mẫu đều được hỗ trợ đầy đủ trong C99. Cái cũ đã không còn được dùng nữa kể từ C89 / 90, nếu tôi nhớ không lầm. C99 yêu cầu hàm phải được khai báo trước lần sử dụng đầu tiên, nhưng khai báo không bắt buộc phải là nguyên mẫu. Sự nhầm lẫn dường như bắt nguồn từ sự pha trộn thuật ngữ phổ biến: nhiều người gọi bất kỳ khai báo hàm nào là "nguyên mẫu", trong khi thực tế "khai báo hàm" không giống như "nguyên mẫu".