我正在使用以下代码:
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;
}
此处do_something
的实施并不重要。
当我尝试编译上面的内容时,我得到了以下两个例外:
错误:'do_something'的冲突类型(在printf调用时)
错误:'do_something'的先前隐式声明在这里(在原型行中)
为什么?
答案 0 :(得分:114)
您在声明之前尝试调用do_something。您需要在printf行之前添加函数原型:
char* do_something(char*, const char*);
或者您需要将函数定义移到printf行上方。在声明函数之前不能使用函数。
答案 1 :(得分:21)
在“经典”C语言(C89 / 90)中,当您调用未声明的函数时,C假定它返回int
并且还尝试从实际参数的类型派生其参数的类型(不,它没有假设它没有参数,正如之前有人建议的那样。)
在您的具体示例中,编译器将查看do_something(dest, src)
调用并隐式派生do_something
的声明。后者看起来如下
int do_something(char *, char *)
但是,稍后在代码中您明确声明do_something
为
char *do_something(char *, const char *)
如您所见,这些声明彼此不同。这就是编译器不喜欢的。
答案 2 :(得分:6)
在使用它之前,你没有声明它。
你需要像
这样的东西char *do_something(char *, const char *);
在printf之前。
示例:
#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;
}
或者,您可以将整个do_something
函数放在printf之前。
答案 3 :(得分:6)
您必须在使用之前声明该功能。如果函数名称在声明之前出现,C编译器将遵循某些规则并自行声明。如果错了,您将收到该错误。
您有两种选择:(1)在使用之前定义它,或(2)使用前向声明而不实现。例如:
char *do_something(char *dest, const char *src);
注意最后的分号。
答案 4 :(得分:4)
C诫命#3:
K&R #3 Thou shalt always prototype your functions or else the C compiler will extract vengence.
答案 5 :(得分:4)
C函数声明背景
在C中,函数声明不像其他语言那样工作:C编译器本身不会在文件中向后和向前搜索,以便从您所在的位置查找函数声明调用它,并且它不会多次扫描文件以找出关系:编译器只扫描文件中的 forward ,从顶部到底部。连接函数调用函数声明是链接器作业的一部分,只有在将文件编译成原始汇编指令后才能
。这意味着当编译器向前扫描文件时,编译器第一次遇到函数名称时,必然会出现以下两种情况之一:它要么看到函数声明本身,在这种情况下编译器确切地知道函数是什么以及它作为参数采用什么类型以及它返回什么类型 - 或者它调用函数,并且编译器必须猜测函数最终如何宣布。
(第三个选项,其中名称用于函数原型,但我们暂时忽略它,因为如果您首先看到这个问题,那么&# 39;可能不使用原型。)
历史课
在C的早期,编译器必须猜测类型的事实并不是真正的问题:所有类型都或多或少相同 - 几乎所有东西都是int或者指针,它们的大小相同。 (事实上,在B之前,C语言之前的语言,根本没有类型;一切都只是一个int或指针,它的类型完全取决于你如何使用它!)所以编译器可以安全地猜测任何行为函数只是基于传递的参数的数量:如果你传递了两个参数,编译器会将两个东西推到调用堆栈上,并且可能被调用者会声明两个参数,并且这些参数都会排列。如果你只传递了一个参数但是函数预期为2,那么它仍然可以排序,而第二个参数只会被忽略/垃圾。如果您传递了三个参数且函数预期为2,那么它仍然可以进行排序,并且第三个参数将被函数的局部变量忽略并踩踏。 (一些旧的C代码仍然期望这些不匹配的参数规则也会起作用。)
但是让编译器让你将任何东西传递给任何东西并不是设计编程语言的好方法。它在早期运作良好,因为早期的C程序员大多是向导,并且他们知道不会将错误的类型传递给函数,即使他们确实错误地输入了类型,也总是有lint
这样的工具可以对你的C代码进行更深入的仔细检查,并警告你这些事情。
快进到今天,我们并不是在同一条船上。 C已经长大了,很多人都在为它编程而不是向导,为了适应它们(以及容纳其他经常使用lint
的人),编译器已经采用了很多之前属于lint
的能力 - 尤其是他们检查您的代码以确保其类型安全的部分。早期的C编译器会让你编写int foo = "hello";
,它会轻易地将指针分配给整数,并且由你来确保你没有做任何愚蠢的事情。当你的类型错误时,现代C编译器会大声抱怨,这是件好事。
输入冲突
那么所有这些与函数声明中的神秘冲突类型错误有什么关系呢?正如我上面所说,C编译器仍然必须知道或猜测当他们第一次在文件中向前扫描时看到名称的含义:他们可以< em>知道如果它是一个真正的函数声明本身(或者一个函数&#34;原型,&#34;更多关于它的内容)它意味着什么,但如果它只是它调用该函数,他们必须猜测。而且,遗憾的是,猜测往往是错误的。
当编译器看到你对do_something()
的调用时,它查看了它是如何被调用的,并得出结论do_something()
最终将被声明为:
int do_something(char arg1[], char arg2[])
{
...
}
为什么结论呢?因为那就是你所谓的它! (有些C编译器可能会认为它是int do_something(int arg1, int arg2)
,或者只是int do_something(...)
,这两者都是你想要的更远,但重要的是无论如何编译器猜测类型,它猜测它们与实际函数使用的不同。)
稍后,当编译器在文件中向前扫描时,它会看到char *do_something(char *, char *)
的实际声明。该函数声明甚至不接近编译器猜测的声明,这意味着编译器编译调用的行编译错误,程序无法正常工作。所以它正确地输出了一个错误,告诉你你的代码没有按照书面形式工作。
你可能想知道,&#34;为什么我假设我正在回复int
?&#34;好吧,它假定类型,因为没有相反的信息:printf()
可以在其变量参数中接受任何类型,因此没有更好的答案,{{1}和任何人一样好。 (许多早期的C编译器总是假定int
用于每个未指定的类型,并且假设您对于声明int
的每个函数的参数都是...
- 而不是f()
- 这就是为什么许多现代代码标准建议总是将void
置于参数中,如果真的不应该是参数。)
修复
函数声明错误有两种常见的修复方法。
这里的许多其他答案推荐的第一个解决方案是将原型放在首先调用该函数的源代码上面。原型看起来就像函数的声明,但它有一个分号,其中正文应该是:
void
通过首先放置原型,编译器然后知道函数最终会是什么样的,所以它不必猜测。按照惯例,程序员经常将原型放在文件的顶部,就在char *do_something(char *dest, const char *src);
语句之下,以确保在任何潜在用法之前始终定义它们。
另一个解决方案,也出现在一些现实世界的代码中,只是简单地重新排序你的函数,以便函数声明总是之前调用它们的任何东西!您可以将整个#include
函数移动到第一次调用之上,然后编译器会确切地知道函数的样子并且不必猜测。
实际上,大多数人都使用函数原型,因为您还可以使用函数原型并将它们移动到头文件(char *do_something(char *dest, const char *src) { ... }
)中,以便其他.h
文件中的代码可以调用这些函数。但是,无论哪种解决方案都有效,许多代码库都使用它们。
C99和C11
值得注意的是,在较新版本的C标准中,规则略有不同。在早期版本(C89和K&amp; R)中,编译器确实将猜测函数调用时的类型(并且K&amp; R-era编译器通常甚至不会警告您错了)。 C99和C11都要求函数声明/原型必须在第一次调用之前,如果它没有,则它是一个错误。但是许多现代C编译器 - 主要是为了与早期代码向后兼容 - 只会警告关于缺少原型并且不认为它是错误。
答案 6 :(得分:3)
如果在使用之前没有为函数提供原型,C假定它接受任意数量的参数并返回int。所以当你第一次尝试使用do_something时,这就是编译器正在寻找的函数类型。这样做会产生关于“隐式函数声明”的警告。
所以在你的情况下,当你实际上稍后声明函数时,C不允许函数重载,所以它变得很糟糕,因为你已经声明了两个具有不同原型但具有相同名称的函数。
简短回答:在尝试使用之前声明该函数。
答案 7 :(得分:3)
再次观看:
char dest[5];
char src[5] = "test";
printf("String: %s\n", do_something(dest, src));
专注于这一行:
printf("String: %s\n", do_something(dest, src));
您可以清楚地看到 do_something 功能未声明!
如果你再看一点,
printf("String: %s\n", do_something(dest, src));
char *do_something(char *dest, const char *src)
{
return dest;
}
您将看到在使用之后声明功能。
您需要使用以下代码修改此部分:
char *do_something(char *dest, const char *src)
{
return dest;
}
printf("String: %s\n", do_something(dest, src));
干杯;)
答案 8 :(得分:1)
当您修改c函数定义并忘记更新相应的标头定义时,通常会发生这种情况。
答案 9 :(得分:0)
确保首先声明函数声明中的类型。
/* start of the header file */
。
。
。
struct intr_frame{...}; //must be first!
。
。
。
void kill (struct intr_frame *);
。
。
。
/* end of the header file */