88

我正在使用以下代码:

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”的先前隐式声明在这里(在原型行)

为什么?

4

11 回答 11

148

您试图在声明它之前调用 do_something 。您需要在 printf 行之前添加一个函数原型:

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

或者您需要将函数定义移到 printf 行上方。在声明之前,您不能使用函数。

于 2009-10-11T02:01:42.440 回答
26

在“经典”C 语言 (C89/90) 中,当您调用未声明的函数时,C 假定它返回 anint并且还尝试从实际参数的类型派生其参数的类型(不,它不假定正如之前有人建议的那样,它没有参数)。

在您的具体示例中,编译器将查看do_something(dest, src)call 并隐式派生do_something. 后者如下所示

int do_something(char *, char *)

但是,稍后在您明确声明do_something为的代码中

char *do_something(char *, const char *)

如您所见,这些声明彼此不同。这是编译器不喜欢的。

于 2009-10-11T02:33:31.533 回答
16

AC 函数声明后台程序

在 C 中,函数声明不像在其他语言中那样工作:C 编译器本身不会在文件中向后和向前搜索以从您调用它的位置找到函数的声明,并且它不会扫描文件多次找出关系:编译器只在文件中从上到下准确地向前扫描一次。将函数调用连接到函数声明是链接器工作的一部分,并且仅文件被编译为原始汇编指令之后才完成。

这意味着当编译器向前扫描文件时,编译器第一次遇到函数的名称时,必须是以下两种情况之一:要么是看到函数声明本身,在这种情况下编译器知道这个函数到底是什么,它接受什么类型作为参数,返回什么类型——或者它是对函数的调用,编译器必须猜测函数最终将如何声明。

(还有第三种选择,在函数原型中使用名称,但我们现在将忽略它,因为如果您首先看到这个问题,那么您可能没有使用原型。)

历史课

在 C 的早期,编译器必须猜测类型这一事实并不是真正的问题:所有类型或多或少都相同——几乎所有东西都是 int 或指针,它们是相同的大小。(事实上​​,在 C 之前的语言 B 中,根本没有类型;一切都只是一个 int 或指针,其类型完全取决于您如何使用它!)因此编译器可以安全地猜测任何类型的行为仅基于传递的参数数量的函数:如果传递了两个参数,编译器会将两个东西压入调用堆栈,并且可能被调用者声明了两个参数,并且所有参数都会排成一行。如果你只传递了一个参数,但函数需要两个,它仍然可以工作,第二个参数将被忽略/垃圾。如果您传递了三个参数而函数需要两个参数,它仍然可以正常工作,并且第三个参数将被函数的局部变量忽略并踩踏。(一些旧的 C 代码仍然希望这些不匹配的参数规则也能起作用。)

但是让编译器让你将任何东西传递给任何东西并不是设计编程语言的好方法。它在早期运行良好,因为早期的 C 程序员大多是巫师,他们知道不要将错误的类型传递给函数,即使他们确实弄错了类型,也总有类似的工具lint可以进行更深入的双重检查您的 C 代码并警告您此类事情。

快进到今天,我们并不完全在同一条船上。C 语言已经发展起来,很多人都在用它进行编程,他们不是巫师,为了适应他们(并适应其他经常使用的人lint),编译器已经具备了许多以前属于 C 语言的能力。lint——尤其是他们检查您的代码以确保其类型安全的部分。早期的 C 编译器会让你编写int foo = "hello";代码,它只会愉快地将指针分配给整数,这取决于你确保你没有做任何愚蠢的事情。当您的类型错误时,现代 C 编译器会大声抱怨,这是一件好事。

类型冲突

那么这一切与函数声明行上神秘的冲突类型错误有什么关系呢?正如我上面所说的,C 编译器在向前扫描文件时第一次看到该名称时仍然必须知道猜测该名称的含义:如果它是实际的函数声明本身(函数“原型”稍后会详细介绍),但如果它只是对函数的调用,他们必须猜测. 而且,可悲的是,猜测往往是错误的。

当编译器看到您对 的调用时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 *). 该函数声明甚至与编译器猜测的声明都不接近,这意味着编译器编译调用的行编译错误,程序无法运行。所以它正确地打印一个错误,告诉你你的代码不会像写的那样工作。

您可能想知道,“为什么它假设我要返回一个int?” 好吧,它假设该类型,因为没有相反的信息: printf()可以在其变量参数中接受任何int类型,因此没有更好的答案,就像任何猜测一样好。(许多早期的 C 编译器总是假设int每个未指定的类型,并假设您的意思是...为每个声明的函数的参数f()- 不是void- 这就是为什么许多现代代码标准建议总是void输入参数,如果真的不应该有任何.)

修复

函数声明错误有两个常见的修复方法。

第一个解决方案,这里许多其他答案都推荐,是在源代码中第一次调用函数的地方上方放置一个原型。原型看起来就像函数的声明,但它的主体应该是一个分号:

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

通过把原型放在首位,编译器就知道函数最终会是什么样子,所以它不必猜测。按照惯例,程序员通常将原型放在文件的顶部,就在#include语句的下方,以确保它们总是在它们的任何潜在用途之前被定义。

另一个解决方案,也出现在一些真实世界的代码中,是简单地重新排序你的函数,以便函数声明总是在调用它们的任何东西之前!您可以将整个char *do_something(char *dest, const char *src) { ... }函数移到第一次调用它的上方,然后编译器将确切地知道该函数的外观,而不必猜测。

在实践中,大多数人都使用函数原型,因为您也可以将函数原型移到头文件 ( .h) 中,以便其他.c文件中的代码可以调用这些函数。但任何一种解决方案都有效,许多代码库同时使用这两种解决方案。

C99 和 C11

值得注意的是,新版本的 C 标准中的规则略有不同。在较早的版本(C89 和 K&R)中,编译器确实在函数调用时猜测类型(而 K&R 时代的编译器通常甚至不会在错误时警告您)。C99 和 C11 都要求函数声明/原型必须在第一次调用之前,如果不这样做就是错误。但是许多现代 C 编译器——主要是为了向后兼容早期代码——只会警告缺少原型而不认为它是错误。

于 2017-04-06T16:20:57.617 回答
8

你在使用它之前没有声明它。

你需要类似的东西

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 之前。

于 2009-10-11T02:00:50.880 回答
6

您必须在使用之前声明该函数。如果函数名出现在其声明之前,C 编译器将遵循一定的规则并自行进行声明。如果它是错误的,你会得到那个错误。

您有两种选择:(1)在使用之前定义它,或者(2)使用前向声明而不实现。例如:

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

注意最后的分号。

于 2009-10-11T02:02:57.180 回答
4

C 戒律#3:

K&R #3 Thou shalt always prototype your functions or else the C compiler will extract vengence. 

http://www.ee.ryerson.ca:8080/~elf/hack/God.vs.K+R.html

于 2009-10-11T10:46:54.310 回答
4

再看一遍:

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));

干杯;)

于 2015-08-08T11:00:58.363 回答
3

如果在使用函数之前没有给出函数原型,C 会假定它接受任意数量的参数并返回一个 int。因此,当您第一次尝试使用 do_something 时,这就是编译器正在寻找的函数类型。这样做应该会产生关于“隐式函数声明”的警告。

因此,在您的情况下,当您稍后实际声明该函数时,C 不允许函数重载,因此它变得很生气,因为您已经声明了两个具有不同原型但名称相同的函数。

简短的回答:在尝试使用之前声明函数。

于 2009-10-11T02:05:58.577 回答
1

当您修改 ac 函数定义而忘记更新相应的头文件定义时,通常会发生这种情况。

于 2016-07-07T21:59:30.063 回答
0

确保首先声明函数声明中的类型。

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

于 2012-03-22T15:37:04.747 回答
0

#include <arpa/inet.h>

对于“inet_ntoa”的objective-c冲突类型

于 2021-09-14T16:18:34.573 回答