【问题标题】:multiple function declarations in different compilation units不同编译单元中的多个函数声明
【发布时间】:2013-05-13 15:48:07
【问题描述】:

零.c:

    int sq();

one.c:

    int sq(int i) { return i*i; }

两个.c:

    int sq(int i, int j);

main.c:

    int main() {
        printf("%d\n",sq());
        printf("%d\n",sq(2));
        printf("%d\n",sq(2,3));
    }

然后我分别编译每个文件和gcc zero.o one.o two.o main.o -o main

./main给了

1
4
4

我对这将如何成功运作感到有些困惑。当我打电话给sq()sq(2)sq(2,3) 时会发生什么

【问题讨论】:

  • two.c 的实现是什么?
  • 我认为您的链接器很困惑。我不认为你实际上可以有 3 个方法命名相同的东西,即使它们确实有不同的参数。我认为您的目标代码将调用其中一个函数,而不是全部 3 个。我认为函数 1 被调用,然后其余的是函数 2 调用。
  • 如果你这样做会有未定义的结果:stackoverflow.com/questions/3098380/…
  • 您使用的是哪个编译器和版本?
  • 您只有一个 sq() 的实际实现,因此每次调用该函数时都会使用它。如果您使用错误数量的参数调用它,您将有未定义的行为。

标签: c linker


【解决方案1】:

如果您想知道真正发生了什么,请让 gcc 输出 main.o 的程序集并查看一下。我想你会发现当你调用 sq() 时,参数被加载到你机器上的基址寄存器中,然后 sq(int i) 将在第一个寄存器上执行乘法指令。如果你传递额外的参数,它们不会有任何影响,如果你不传递任何参数,它只会对之前加载到该寄存器中的任何值起作用。

【讨论】:

    【解决方案2】:

    zero.c & two.c 没有任何函数定义。这只是原型声明。因此,它不会创建任何具有函数定义的汇编代码。 (提示:编译时使用gcc -s 标志进行验证。)

    只有 two.c 有函数定义。因此 two.s 将有一个函数sq,它接受第一个参数(通常在堆栈或处理器的第一个寄存器上传递,如 intel 上的 eax 或 arm 中的 r0)并返回其平方。

    由于你没有在 main.c 中给出任何原型,编译器 (.c -> .s) 不会抱怨。它可能可能将其视为int sq(...),但我不确定。

    因此,对于 3 个不同的输入: sq()sq(2)sq(2,3) 都将调用同一个函数,该函数在 two.c 中声明。

    现在,sq(2)sq(2,3) 的输出很明显 - 返回传递的第一个参数的平方。 sq() 的输出将取决于在 sq 的堆栈中看到的堆栈/eax/r0 上的内容。好像是1.提示:在gdb下运行验证。

    【讨论】:

    • zero.c 没有任何原型,只是一个声明。
    • 我曾经互换使用原型和声明这两个术语...你能告诉我区别吗?
    • 原型是带有参数列表的函数声明。 foo() 并不意味着foo 不接受任何参数,而是它可以接受任意数量的任何类型的参数(显然不使用它们,因为没有参数)。在 C 语言中不带参数的函数原型类似于 foo(void),您可以在 SO 上找到有关此差异的一些问题。
    • 我明白了...我想知道,仅声明有什么用?只是为了告诉存在一些名为 foo 的函数?
    • 从 C89 我看不出有什么理由不使用原型,非原型函数声明仍然存在以实现向后兼容性:在标准化之前 (K&R C)没有原型,它们带有 C89。
    【解决方案3】:

    根据 C 规范,您的代码以多种方式(可能还有更多方式)调用未定义的行为:

    • 同一作用域中同一函数的两个声明使用不同的返回或参数类型/计数
    • 对于在范围内没有函数原型的函数的调用,参数的数量不等于参数的数量

    由于这是未定义的行为,您无法预测会发生什么。结果甚至不一定要一致。如果您没有看到此代码的编译时警告/错误,则需要调高编译器的警告级别。

    在 main.c 中添加原型可能会使用此代码解决编译器的警告。但是,链接器可能仍然存在问题,因为您在同一范围内有多个同名的函数,而且您不完全清楚您希望代码使用哪一个。

    【讨论】:

      【解决方案4】:

      所以,我之前根据我在帖子中读到的内容写了一个答案。这是错误的。这是正确的答案。

      zero.c 不会生成任何代码。 two.c 不生成任何代码。

      main.c 和 one.c 是唯一实际生成代码的文件。

      在 one.c 中使用一个参数 sq(int i) 调用一个没有参数的函数是未定义的行为(因此“任何事情都可能发生”,包括在某些情况下类似于您所期望的事情)。使用两个参数调用也是未定义的行为 - 同样,当你这样做时它不一定会“出错”,但你不能保证它工作(或做你期望的) - 例如它也可以返回 9 因为它将参数从最后一个到第一个放入寄存器。

      【讨论】:

      • 不同意...只有 1 个定义。 zero.c 和 two.c 只有原型。
      猜你喜欢
      • 2017-08-10
      • 2011-12-22
      • 1970-01-01
      • 2018-03-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-06
      • 1970-01-01
      相关资源
      最近更新 更多