2009-10-11 28 views
50

Tôi đang sử dụng mã bên dưới:Nhận "các loại xung đột cho chức năng" trong C, tại sao?

char dest[5]; 
char src[5] = "test"; 

printf("String: %s\n", do_something(dest, src)); 

char *do_something(char *dest, const char *src) 
{ 
    return dest; 
} 

Việc thực hiện do_something là không quan trọng ở đây. Khi tôi cố gắng biên dịch ở trên, tôi nhận được hai ngoại lệ sau:

error: conflicting types for 'do_something' (at the printf call)
error: previous implicit declaration of 'do_something' was here (at the prototype line)

Tại sao?

Trả lời

92

Bạn đang cố gắng gọi do_something trước khi khai báo. Bạn cần phải thêm mẫu thử nghiệm chức năng trước dòng printf của mình:

char* do_something(char*, const char*); 

Hoặc bạn cần di chuyển định nghĩa hàm trên dòng printf. Bạn không thể sử dụng hàm trước khi nó được khai báo.

+1

ohhh, một sai lầm què như vậy, nhờ – goe

+9

Mọi người đều làm cho sai lầm què nhân dịp ... –

6

Bạn không khai báo trước khi sử dụng.

Bạn cần một cái gì đó giống như

char *do_something(char *, const char *); 

trước khi printf.

Ví dụ:

#include <stdio.h> 
char *do_something(char *, const char *); 
char dest[5]; 
char src[5] = "test"; 
int main() 
{ 
printf("String: %s\n", do_something(dest, src)); 
return 0; 
} 

char *do_something(char *dest, const char *src) 
{ 
return dest; 
} 

Ngoài ra, bạn có thể đặt toàn bộ do_something chức năng trước khi printf.

6

Bạn phải khai báo hàm trước khi sử dụng. Nếu tên hàm xuất hiện trước khi khai báo, trình biên dịch C sẽ tuân theo các quy tắc nhất định và tự khai báo. Nếu sai, bạn sẽ gặp lỗi đó.

Bạn có hai tùy chọn: (1) xác định nó trước khi sử dụng hoặc (2) sử dụng khai báo chuyển tiếp mà không cần triển khai. Ví dụ:

char *do_something(char *dest, const char *src); 

Lưu ý dấu chấm phẩy ở cuối.

2

Khi bạn không đưa ra một mẫu thử nghiệm cho hàm trước khi sử dụng nó, C giả sử rằng nó lấy bất kỳ số tham số nào và trả về một int. Vì vậy, khi bạn lần đầu tiên cố gắng sử dụng do_something, đó là loại hàm mà trình biên dịch đang tìm kiếm. Làm điều này sẽ tạo ra một cảnh báo về "khai báo hàm ngầm". Vì vậy, trong trường hợp của bạn, khi bạn thực sự tuyên bố chức năng sau này, C không cho phép quá tải chức năng, do đó, nó được pissy vì nó đã tuyên bố hai chức năng với nguyên mẫu khác nhau nhưng có cùng tên.

Câu trả lời ngắn: khai báo hàm trước khi thử sử dụng.

+3

Không, nó giả phải mất bất kỳ số lượng các tham số và trả về một int. Trong C, có sự khác biệt lớn giữa 'int foo()' (lấy bất kỳ số tham số nào) và 'int foo (void)' (không có tham số). –

+0

Cảm ơn bạn đã làm rõ! Cập nhật :) – mrduclaw

17

Trong ngôn ngữ C "cổ điển" (C89/90) khi bạn gọi hàm chưa khai báo, C giả định rằng hàm trả về int và cũng cố gắng lấy các loại tham số của nó từ các loại đối số thực tế (không, nó không cho rằng nó không có tham số, như ai đó đã đề xuất trước đó).

Trong ví dụ cụ thể của bạn trình biên dịch sẽ xem xét số do_something(dest, src) cuộc gọi và ngầm lấy được tuyên bố cho do_something.Sau đó sẽ trông như sau

int do_something(char *, char *) 

Tuy nhiên, sau đó trong mã bạn một cách rõ ràng tuyên bố do_something như

char *do_something(char *, const char *) 

Như bạn có thể thấy, những tờ khai khác nhau từ mỗi khác. Đây là những gì trình biên dịch không thích.

+0

Có một số trường hợp bạn có thể gọi một chức năng không khai báo hợp pháp không? Lý do đằng sau lỗi "xung đột loại" cho vấn đề này thay vì lỗi "chức năng không được khai báo trước khi được gọi là" là gì? – brandaemon

+1

@ brandaemon: Ngôn ngữ C gốc (C89/90) không có lỗi "chức năng không được khai báo trước khi được gọi". Hoàn toàn hợp pháp để gọi các hàm không khai báo trong C. Thay vì phát ra lỗi, ngôn ngữ được yêu cầu để phân tích cuộc gọi và "suy luận" khai báo * ngầm định cho hàm. Đó là cách ngôn ngữ được thiết kế ban đầu. Vì vậy, có trong C89/90 bạn có thể gọi một cách hợp pháp một chức năng không khai báo, miễn là khai báo chức năng "suy luận" là * tương thích * với thực tế. – AnT

+1

@ brandaemon: C99 bị cấm gọi các chức năng không khai báo. Có nghĩa là bắt đầu với C99, có "chức năng không được khai báo trước khi gọi" lỗi trong C. Tuy nhiên, ngay cả trong C99 (và sau này) nó có thể tuyên bố một chức năng mà không có một nguyên mẫu. I E. nó là đủ để làm 'int foo()' để làm cho hàm 'foo' chính thức * được khai báo *. Trong C (trái ngược với C++) '()' trong khai báo hàm vẫn có nghĩa là danh sách tham số hàm là * không xác định * và phải được "suy luận" bởi trình biên dịch chính xác như mô tả ở trên. I E. Các trình biên dịch C không còn suy ra các kiểu trả về hàm, nhưng các danh sách tham số vẫn có thể được suy ra. – AnT

0

Hãy chắc chắn rằng loại trong phần khai báo hàm được khai báo đầu tiên.

/* start of the header file */
.
.
.
struct intr_frame{...}; //must be first!
.
.
.
void kill (struct intr_frame *);
.
.
.
/* end of the header file */

3

Xem lại:

char dest[5]; 
char src[5] = "test"; 

printf("String: %s\n", do_something(dest, src)); 

Tập trung vào dòng này:

printf("String: %s\n", do_something(dest, src)); 

Bạn có thể thấy rõ rằng do_something chức năng không được khai báo!

Nếu bạn nhìn xa hơn một chút,

printf("String: %s\n", do_something(dest, src)); 

char *do_something(char *dest, const char *src) 
{ 
return dest; 
} 

bạn sẽ thấy rằng bạn khai báo các chức năng sau bạn sử dụng nó.

Bạn sẽ cần phải sửa đổi phần này với mã này:

char *do_something(char *dest, const char *src) 
{ 
return dest; 
} 

printf("String: %s\n", do_something(dest, src)); 

Cheers;)

0

Điều này thường xảy ra khi bạn thay đổi một định nghĩa hàm c và quên cập nhật các định nghĩa tiêu đề tương ứng.

1

AC Chức năng-Tuyên bố Backgrounder

Trong C, tờ khai chức năng không làm việc như họ làm trong các ngôn ngữ khác: Trình biên dịch C tự nó không tìm kiếm lạc hậu và chuyển tiếp trong tập tin để tìm khai của chức năng từ nơi bạn gọi nó, và nó không quét tệp nhiều lần để tìm ra mối quan hệ: Trình biên dịch chỉ quét chuyển tiếp trong tệp chính xác một lần, từ trên xuống dưới. Việc kết nối các cuộc gọi hàm với các khai báo hàm là một phần của công việc của nhà liên kết và chỉ được thực hiện sau tệp được biên dịch xuống các hướng dẫn lắp ráp nguyên bản. Điều này có nghĩa là khi trình biên dịch quét về phía trước thông qua tệp, lần đầu tiên trình biên dịch gặp tên của một hàm, một trong hai điều phải là trường hợp: Nó hoặc là nhìn thấy khai báo hàm, trong đó trường hợp trình biên dịch biết chính xác hàm này là gì và kiểu nào nó lấy làm đối số và kiểu nó trả về - hoặc đó là lời gọi hàm, và trình biên dịch phải đoán cách hàm sẽ được khai báo cuối cùng.

(Có một tùy chọn thứ ba, trong đó tên được sử dụng trong nguyên mẫu hàm, nhưng chúng tôi sẽ bỏ qua điều đó ngay bây giờ, vì nếu bạn gặp vấn đề này ngay từ đầu, có thể bạn không sử dụng nguyên mẫu.)

Lịch sử Bài học

trong những ngày đầu của C, thực tế là trình biên dịch phải đoán loại là không thực sự là một vấn đề: Tất cả các loại đều hơn hoặc ít hơn như nhau - khá mọi thứ đều là một int hoặc một con trỏ, và chúng có cùng kích thước. (Trong thực tế, trong B, ngôn ngữ trước C, không có loại nào cả; mọi thứ chỉ là int hoặc con trỏ và kiểu của nó được xác định chỉ bằng cách bạn sử dụng nó!) Vậy thì trình biên dịch có thể đoán được hành vi của bất kỳ chỉ dựa trên số tham số đã được truyền: Nếu bạn truyền hai tham số, trình biên dịch sẽ đẩy hai thứ vào ngăn xếp cuộc gọi, và có lẽ callee sẽ có hai đối số được khai báo và tất cả sẽ được xếp hàng. Nếu bạn chỉ truyền một tham số nhưng hàm được mong đợi hai, nó sẽ vẫn sắp xếp công việc và đối số thứ hai sẽ bị bỏ qua/rác. Nếu bạn đã vượt qua ba tham số và hàm được mong đợi hai, nó cũng sẽ sắp xếp công việc, và tham số thứ ba sẽ bị bỏ qua và được dẫm bởi các biến cục bộ của hàm. (Một số mã C cũ vẫn mong đợi các quy tắc đối số không phù hợp này cũng sẽ hoạt động.)

Nhưng có trình biên dịch cho phép bạn chuyển bất cứ thứ gì cho bất kỳ thứ gì không thực sự là một cách hay để thiết kế một ngôn ngữ lập trình. Nó hoạt động tốt trong những ngày đầu bởi vì các lập trình viên C đầu tiên chủ yếu là các trình thuật sĩ, và họ biết không truyền loại sai cho các chức năng, và thậm chí nếu chúng có sai loại, luôn có các công cụ như lint. kiểm tra mã C của bạn và cảnh báo bạn về những điều như vậy.

Tua tới ngày hôm nay và chúng tôi không hoàn toàn giống nhau trên cùng một chiếc thuyền. C đã lớn lên, và rất nhiều người đang lập trình trong đó không phải là pháp sư, và để thích ứng với họ (và để chứa tất cả những người khác thường xuyên sử dụng lint anyway), các trình biên dịch đã thực hiện trên nhiều khả năng mà trước đó là một phần của lint - đặc biệt là phần mà họ kiểm tra mã của bạn để đảm bảo mã an toàn. Các trình biên dịch C ban đầu sẽ cho phép bạn viết int foo = "hello"; và nó sẽ chỉ gán con trỏ cho số nguyên, và điều đó tùy thuộc vào bạn để đảm bảo rằng bạn không làm bất cứ điều gì ngu ngốc. Trình biên dịch C hiện đại phàn nàn lớn tiếng khi bạn nhận được các loại của bạn sai, và đó là một điều tốt.

Loại Xung đột

Vì vậy, những gì đang tất cả điều này đã làm với các lỗi mâu thuẫn kiểu bí ẩn trên dòng khai báo hàm? Như tôi đã nói ở trên, trình biên dịch C vẫn phải biết hoặc đoán tên là gì khi lần đầu tiên họ nhìn thấy tên khi họ quét chuyển tiếp qua tệp: Họ có thể biết ý nghĩa của nó là gì tự khai báo hàm (hoặc một hàm "nguyên mẫu", thêm vào đó ngay), nhưng nếu nó chỉ là một lời gọi hàm, chúng phải đoán. Và, thật đáng buồn, đoán thường sai.

Khi trình biên dịch thấy cuộc gọi của bạn để do_something(), nó nhìn nó như thế nào được gọi, và nó kết luận rằng do_something() cuối cùng sẽ được công bố như thế này:

int do_something(char arg1[], char arg2[]) 
{ 
    ... 
} 

Tại sao nó kết luận rằng? Vì đó là cách bạn gọi là! (Một số trình biên dịch C có thể kết luận rằng đó là int do_something(int arg1, int arg2) hoặc đơn giản là int do_something(...), cả hai đều là xa hơn từ những gì bạn muốn, nhưng điều quan trọng là bất kể trình biên dịch đoán các loại như thế nào, nó đoán chúng khác với sử dụng chức năng thực tế.)

Sau đó, khi trình biên dịch quét về phía trước trong tệp, nó sẽ xem tờ khai thực tế của bạn là char *do_something(char *, char *). Khai báo hàm đó thậm chí không gần với khai báo mà trình biên dịch đoán, có nghĩa là dòng nơi trình biên dịch đã biên dịch cuộc gọi được biên dịch sai, và chương trình sẽ không hoạt động. Vì vậy, nó đúng in một lỗi nói với bạn rằng mã của bạn sẽ không hoạt động như được viết.

Bạn có thể thắc mắc, "Tại sao tôi giả sử tôi trả lại int?" Vâng, nó giả định rằng loại vì không có thông tin ngược lại: printf() có thể lấy bất kỳ loại nào trong các đối số biến của nó, vì vậy mà không có câu trả lời tốt hơn, int cũng tốt như dự đoán. (Nhiều trình biên dịch C ban đầu luôn giả định int đối với mọi loại không xác định và giả sử bạn có nghĩa là ... cho các đối số cho mọi hàm được khai báo f() - không phải là void - đó là lý do tại sao nhiều tiêu chuẩn mã hiện đại đề xuất luôn đặt void cho đối số nếu có t cho là bất kỳ.)

Cách khắc phục

có hai bản vá phổ biến đối với các lỗi chức năng kê khai.

Các giải pháp đầu tiên, đó là khuyến cáo của nhiều câu trả lời khác ở đây, là để đặt một nguyên mẫu trong mã nguồn trên nơi hàm được đầu tiên gọi. Một nguyên mẫu trông giống như tuyên bố của chức năng, nhưng nó có một dấu chấm phẩy nơi cơ thể nên là:

char *do_something(char *dest, const char *src); 

Bằng cách đặt nguyên mẫu đầu tiên, trình biên dịch sau đó biết những gì các chức năng cuối cùng sẽ như thế nào, vì thế nó doesn không phải đoán.Theo quy ước, các lập trình viên thường đặt các nguyên mẫu ở đầu tệp, ngay dưới các câu lệnh #include, để đảm bảo rằng chúng sẽ luôn được xác định trước bất kỳ tập quán tiềm năng nào của chúng.

Giải pháp khác, cũng hiển thị trong một số mã thực, chỉ đơn giản là sắp xếp lại các hàm của bạn để các khai báo hàm luôn là trước bất kỳ thứ gì gọi chúng! Bạn có thể di chuyển toàn bộ hàm char *do_something(char *dest, const char *src) { ... } phía trên cuộc gọi đầu tiên đến nó, và trình biên dịch sau đó sẽ biết chính xác chức năng trông như thế nào và sẽ không phải đoán. Trong thực tế, hầu hết mọi người sử dụng nguyên mẫu chức năng, vì bạn cũng có thể lấy nguyên mẫu hàm và di chuyển chúng vào tiêu đề (.h) để mã trong các tệp .c khác có thể gọi những chức năng đó. Nhưng một trong hai giải pháp hoạt động, và nhiều codebase sử dụng cả hai.

C99 và C11

Nó rất hữu ích cần lưu ý rằng các quy tắc hơi khác nhau trong các phiên bản mới hơn của tiêu chuẩn C. Trong các phiên bản trước (C89 và K & R), trình biên dịch thực sự sẽ đoán các loại tại thời gian gọi hàm (và K & Trình biên dịch thời đại thường sẽ không cảnh báo bạn nếu chúng sai). Cả C99 và C11 đều yêu cầu khai báo/nguyên mẫu hàm phải đứng trước cuộc gọi đầu tiên và đó là lỗi nếu nó không xảy ra. Nhưng nhiều trình biên dịch C hiện đại - chủ yếu cho khả năng tương thích ngược với mã trước đó - sẽ chỉ cảnh báo về nguyên mẫu bị thiếu và không coi đó là lỗi.

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