【问题标题】:Getting "conflicting types for function" in C, why?在 C 中获取“函数的冲突类型”,为什么?
【发布时间】:2010-12-05 16:45:26
【问题描述】:

我正在使用以下代码:

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

为什么?

【问题讨论】:

  • 我遇到了同样的错误,但我还没有在任何地方调用该函数(无论是在定义之前还是之后),因此所选答案不适用于我的情况。原来问题是命名冲突——我的函数名称是“mergesort”(我正在关注 Skiena 的“算法设计手册”),并且在 stdlib.h 中已经有一个同名的函数。将我的函数重命名为“merge_sort”就可以了。

标签: c arrays


【解决方案1】:

你没有在使用前声明它。

你需要类似的东西

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

【讨论】:

    【解决方案2】:

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

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

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

    【讨论】:

    • 我确实太适应现在的语言了!
    【解决方案3】:

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

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

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

    注意最后的分号。

    【讨论】:

      【解决方案4】:

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

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

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

      【讨论】:

      • 不,它假定它接受任意数量的参数并返回一个 int。在 C 中,int foo()(带任意数量的参数)和int foo(void)(不带参数)之间存在很大差异。
      • 感谢您的澄清!更新了:)
      【解决方案5】:

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

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

      int do_something(char *, char *)
      

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

      char *do_something(char *, const char *)
      

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

      【讨论】:

      • 在某些情况下您可以合法地调用未声明的函数吗?此问题出现“类型冲突”错误而不是“调用前未声明函数”错误的原因是什么?
      • @brandaemon:原始 C 语言 (C89/90) 没有“函数未在调用前声明”错误。在 C 中调用未声明的函数是完全合法的。语言需要分析调用并“推断”函数的 implicit 声明,而不是发出错误。这就是语言最初的设计方式。所以,是的,在 C89/90 中,您可以合法地调用未声明的函数,只要“推导的”函数声明与实际声明兼容
      • @brandaemon:C99 禁止调用未声明的函数。这意味着从 C99 开始,在 C 中存在“函数未在调用前声明”错误。但是,即使在 C99(及更高版本)中,也可以在没有原型的情况下声明函数。 IE。做int foo() 使函数foo 正式声明就足够了。在 C(相对于 C++)中,函数声明中的 () 仍然意味着函数参数列表是未指定,并且必须由编译器完全按照上述方式“推导”。 IE。 C 编译器不再推断函数返回类型,但仍然可以推断参数列表。
      • 好的,我想我明白了。那么这个特定的编译器错误是故意留下来引用类型还是可能已经被改写(并且可能已经在某些编译器中?我刚刚在 gcc 上看到过这种方式)当问题出在函数声明时的特殊情况?我主要是想看看如何更好地解释这个特定的错误,因为至少在这种情况下它看起来不是很直观。
      • @brandaemon:原帖中的编译器错误实际上是指函数类型。它说 deduced 函数类型是 int (char *, char *)actual 函数类型是 char *(char *, const char *)。但是,在这种情况下违反的一般规则适用于所有类型的声明(不仅是“推导”的声明,不仅是函数声明)。例如,您可以声明extern double a;,然后定义int a;,您将收到关于冲突类型的相同错误。而且,显然,GCC 作者认为函数不应该被单独对待。
      【解决方案6】:

      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

      【讨论】:

        【解决方案7】:

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

        /* start of the header file */



        struct intr_frame{...}; //must be first!



        void kill (struct intr_frame *);



        /* end of the header file */

        【讨论】:

          【解决方案8】:

          再看一遍:

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

          干杯 ;)

          【讨论】:

          • 你提到“你会看到你在使用它之后声明了这个函数。”这有点令人困惑。它应该是这样的“你会看到你在调用它之后定义了函数。”
          【解决方案9】:

          当你修改了一个 c 函数定义而忘记更新相应的头文件定义时,经常会发生这种情况。

          【讨论】:

          • 我也发生了类似的事情。我正在编写针对 Khronos cl.h 头文件的 openCL 绑定。我从在线文档中复制和粘贴,为函数原型/签名创建 typedef。结果证明 openCL 1.0 的 clGetContextInfo 的文档不正确。最后一个参数应该是“size_t*”,但实际上是“size_t”。
          【解决方案10】:

          A C 函数声明后台程序

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

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

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

          历史课

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

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

          快进到今天,我们并不完全在同一条船上。 C 已经长大了,很多人都在用它编程,他们不是巫师,为了适应他们(也为了适应其他经常使用lint 的人),编译器已经具备了许多以前的能力以前是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(...),两者都与您想要的相比更远,但重要的一点是,无论编译器如何猜测类型,它猜测它们与你的实际函数使用的不同。)

          稍后,当编译器在文件中向前扫描时,它会看到您的 actual 声明 char *do_something(char *, char *)。该函数声明甚至与编译器猜测的声明都不接近,这意味着编译器编译调用的行编译错误,程序无法运行。所以它正确地打印了一个错误,告诉你你的代码不能像写的那样工作。

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

          修复

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

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

          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 编译器(主要是为了向后兼容早期代码)只会警告缺少原型,而不将其视为错误。

          【讨论】:

          • 感谢您详细周到的回答
          【解决方案11】:

          #include

          对于objective-c 'inet_ntoa' 的冲突类型

          【讨论】:

            猜你喜欢
            • 2013-01-10
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-04-18
            • 1970-01-01
            • 2021-08-28
            • 1970-01-01
            相关资源
            最近更新 更多