【问题标题】:Weird declaration and definition of a function in CC中函数的奇怪声明和定义
【发布时间】:2014-08-22 07:56:23
【问题描述】:

我维护了一个非常古老的 C 项目(编写这些的程序员早就不在了),我发现了这样的东西:

(以// 开头的行给出包含以下行的文件的名称。)

声明:

// db/stor_procs/sp_table.c
/* Special hack prototype */
int32_t put_column_value(table_t * tab, row_t * row, u_int16_t colnum, rt_value_t * v);

// db/triggers/specials.c
/* BAD HACK */
int32_t put_column_value(table_t * tab, row_t * row, u_int16_t colnum, rt_value_t * v);

// db_sean_add_alarm/src/rt_access.c
int32_t put_column_value(struct xput_info *xptr, table_t * tab, row_t * row, u_int16_t colnum, rt_value_t * v);

// db_sean_add_alarm/stor_procs/sp_table.c
/* Special hack prototype */
int32_t put_column_value(table_t * tab, row_t * row, u_int16_t colnum, rt_value_t * v);

定义:

// db/src/rt_access.c
int32_t
put_column_value(struct xput_info *xptr, table_t * tab, row_t * row, u_int16_t colnum, rt_value_t * v){//....}

// db_sean_add_alarm/src/rt_access.c
int32_t
put_column_value(struct xput_info *xptr, table_t * tab, row_t * row, u_int16_t colnum, rt_value_t * v){//.....}

程序员从不在头文件中声明put_column_value,而只在.c 文件中给出定义。更奇怪的是,我找不到将table_t * 作为第一个参数的put_column_value任何 定义,而且我100% 确定table_tstruct xput_info是不同的类型(结构)。

但这还不是全部。最奇怪的是我可以在其他 .c 文件中找到 put_column_value 的声明。现在我需要清理代码,但我无法理解如何处理这种 C 魔法。原程序员在这里使用的“魔法”(hack)是什么?

整个项目有点大,我已经尽力简化问题。如果您需要任何其他信息来解决此问题,请告诉我。

编辑

找出原因,这个项目链接到另一个库,并且那个库有一个函数原型的定义,这就是为什么这个“魔术”(hack)可以找到实现。现在来一个大问题要解决——这个项目没有可靠的单元测试,很难为它开发一个,不测试我不能改变代码,不改变代码我不能做单元测试^_^。啊,维护遗留代码总是不是一件令人愉快的任务。

【问题讨论】:

  • 该函数在“声明”中的第 3 次出现不是声明,而是定义的开始:它缺少末尾的 ;
  • 所有这些 *.c 文件是否都链接到同一个程序或有几个不同的程序?您确定定义中没有 static 关键字吗?以及可能改变代码的宏?
  • @rodrigo 定义中没有静态关键字;该项目确实链接到其他库;找不到任何宏魔法为这些功能做点什么

标签: c


【解决方案1】:

这里有几件事情要担心。

  1. 将原型放在源代码而不是头文件中的骇人听闻的编程风格。这很容易解决(但很乏味)。
  2. 声明和定义不匹配。假设您在原型中有不同数量的参数和函数的两个定义。这些原型似乎很可能没有被实际使用,因为如果它们被使用,程序就会崩溃(假设实际代码没有做一些真正令人厌恶的事情)。所以你可能有机会做一些清理工作。
  3. 您有两个 put_column_value 实现。大概这些是在不应该一起使用的不同库中的。看起来其中一个目录包含带有某种警报的新版本库,但我只是从名称猜测。

【讨论】:

  • 谢谢,我得找出所有调用这些函数的文件,它们是如何链接的,然后把它们放到另一个地方
【解决方案2】:

因此,您感到困惑的第一部分是声明和定义之间的不匹配。

对于构建过程,只要没有编译单元同时看到它们就可以了。但是程序在这个地方不起作用,是吗?

第二部分是关于将声明放入其他 .c 文件中,这种风格非常糟糕。它应该 - 如果有的话 - 只在调试期间我暂时需要另一个文件中的函数时使用。

您可以通过将(正确的)声明放入您需要的头文件中以及 - 为了检测不匹配 - 从您声明它的位置来修复这两个问题。

【讨论】:

    【解决方案3】:

    哎哟!我的猜测是,之前的程序员需要在一个地方向put_column_value 添加一个参数,但懒得到处都这样做。也有可能他已经完成了一半,打算修复其他文件,但在工作完成之前就退出了。

    您应该查看链接器命令以了解哪个定义实际上与程序链接。

    您还应该检查是否曾经(在运行时)从具有错误声明的文件中调用了put_column_value。由于首先放置了额外的参数,只要实现在这些实例中实际上没有使用新参数(如果它写入它,它将破坏堆栈),使用错误声明的调用仍然可以工作。因此,“魔术黑客”。它打破了 C 标准,并在技术上导致了未定义的行为。

    我的建议是,要么在任何地方修复调用,要么将函数分成两个,然后重命名其中一个。无论哪种情况,您都应该将声明移回头文件。

    【讨论】:

    • 参数从右到左传递,所以最左边的参数最后传递。可能是实际/当前的实现没有使用它,所以无论如何它都不会访问它。在 cdecl 调用约定中,这无关紧要。
    • @RudyVelthuis 当然,这是有道理的(否则 va_args 将更难实现)。我责怪我对我找到的插图的草率解释。谢谢!
    猜你喜欢
    • 1970-01-01
    • 2016-02-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-11
    • 1970-01-01
    相关资源
    最近更新 更多