【问题标题】:Cast Between Incompatible Function Types in gccgcc 中不兼容的函数类型之间的强制转换
【发布时间】:2023-04-05 00:17:01
【问题描述】:

我有一个代码生成脚本,它是由其他人在 2008 年左右编写的,从那时起几乎没有变化,运行良好。就在最近我尝试使用 gcc9 进行编译时,我在生成的代码中看到了 7300 条“不兼容函数类型之间的强制转换”警告。

代码采用一组函数指针和各种类型标识符,并将所有内容插入到一个大映射中,稍后用于选项序列化、打印等。许多函数和变量被转换并存储为某种形式的 void *。否则代码编译没有错误并且可以正常工作。

我尝试了各种 C 风格的强制转换、reinterpret_cast 以及将函数指针强制转换为 void(*)(void),但它们都没有消除警告。将函数转换为泛型类型以避免此警告的正确方法是什么?我能想出的唯一解决方案是禁用 -Wcast-function-type。

这是一个生成警告的示例行:

pim.must_find("input_fn")->set_introspect_info( sizeof(filename_t), ((char *)(&p_cn->input_fn)) - ((char *)p_cn), 0,invalid_offset, (str_from_base_t *)str_from_filename_t, (send_base_t *)send_filename_t, (recv_base_t *)recv_filename_t, (val_from_param_t *)val_from_param_filename_t, 0);

警告与这两个功能有关:

typedef std::string str_from_base_t( void const * );

std::string str_from_filename_t( filename_t const & v ) { return v; }

其中 filename_t 是一个从 std::string 插入的类。

请注意,有许多不同的 str_from_***() 函数具有不同的类作为参数。

我得到的警告是:

../src/gen/DEFReader_PostParam.cc: In function 'void croix::DEFReaderCLI_introspect_pim_init()':
../src/gen/DEFReader_PostParam.cc:25:153: warning: cast between incompatible function types from 'std::string (*)(const croix::filename_t&)' {aka 'std::basic_string<char> (*)(const croix::filename_t&)'} to 'std::string (*)(const void*)' {aka 'std::basic_string<char> (*)(const void*)'} [-Wcast-function-type]
25 |  pim.must_find("input_fn")->set_introspect_info( sizeof(filename_t), ((char *)(&p_cn->input_fn)) - ((char *)p_cn), 0,invalid_offset, (str_from_base_t *)str_from_filename_t, (send_base_t *)send_filename_t, (recv_base_t *)recv_filename_t, (val_from_param_t *)val_from_param_filename_t, 0);

【问题讨论】:

  • 将函数转换为泛型类型以避免此警告的正确方法是什么? -- 你为什么认为有一种“正确”的方法?也许你所做的事情是不正确的,到目前为止你还没有被它咬过。应用 C 风格的转换来避免编译器警告和错误是在某些时候可能会出错的迹象。
  • 好的,那么将采用各种不同单类参数的函数指针插入同一个映射的正确方法是什么?我需要找到一种更简洁的方法来完成这项工作,而无需重写所有内容,因为我并不完全了解整个系统应该如何工作。这是大型商业软件代码库的选项处理部分。
  • 本应设计的方式是使用函数对象,并具有函数对象成员的参数部分。然后从基础对象派生,每个子类覆盖operator ()。我知道这不是一个答案,而是作为如何防止将来陷入这种情况的指南。
  • Here is a tiny example。一张地图,多个“功能”,都有不同的签名,绝对没有进行任何转换,也没有void*。诀窍是参数已从函数调用转移到成员变量,operator() 的魔力开始发挥作用。
  • 我需要找到一种更简洁的方法来完成这项工作,而无需重写所有内容,因为我并不完全了解整个系统应该如何工作。 -- I不要认为你需要了解完整的系统。你知道那张地图和演员应该做什么(理论上)。所以这是一个重新实现它的问题,知道最终结果应该是什么。如果您不知道地图的用途、函数指针是什么等,那就另当别论了。

标签: c++ function-pointers


【解决方案1】:

将函数转换为泛型类型的正确方法是什么?

答案是没有正确的方法。警告是绝对有道理的。

关键是你有一个类型安全的函数,你只能传递filename_t,然后你把它转换成可以接受任何东西的东西。不遇到未定义行为的唯一方法是仍然传递filename_t,即使您现在可以传递其他任何内容。

这就产生了为什么需要将其转换为泛型类型的问题?您说这些函数存储在具有某些类型标识符的映射中。现在,由于信息有限,很难对此做出判断,但对我来说,这听起来有点像人为地构建一个为您选择正确函数的机制,而有一个完美的内置机制:函数重载解析。

因此,在代码中的某个时刻,您手头必须有一个filename_t 对象。然后(我假设)您从中推断出一些类型标识符并使​​用它在地图中查找正确的函数。然后你将filename_t 作为void* 传递到那里,知道它会起作用。

我确信它比这复杂得多,但也许您可以识别这部分逻辑并摆脱它:摆脱映射(至少将类型标识符映射到函数的那部分)并调用在你仍然知道类型的时候直接调用函数(因为这样重载解析会为你解决问题)。

模板可以帮助您保留类型而不是制作所有内容void*

【讨论】:

  • 或者,如果动态绑定和virtual 不是问题,则可以使用具有来自基类的重写调用运算符的函数对象。然后不需要将参数转换为void *,因为派生函数对象具有此信息。无论是模板还是动态绑定,OP 的 C 风格转换都是一种绝对不好的方式。
  • 困难的部分是它必须与结构/类的成员变量一起使用,包括整数和浮点数。代码生成构建了一个映射,该映射可用于序列化成员变量集等。它建立在现有代码之上,所以我不能只修改每个类成员以从某个基类继承。
  • 函数重载适用于某些类型,但更复杂的类型是在外部定义的,序列化代码不可用。我收到该函数不存在的编译错误。模板是相似的,不清楚我可以在哪里声明模板函数,以便所有代码都可以访问它们。
  • 作为一个真实的例子,Python C API 的 PyMethodDef 表结构需要这种不兼容的转换。 docs.python.org/3/c-api/structures.html#c.PyMethodDef
猜你喜欢
  • 2017-01-03
  • 1970-01-01
  • 1970-01-01
  • 2015-05-01
  • 2023-04-04
  • 2011-10-14
  • 1970-01-01
  • 1970-01-01
  • 2021-03-08
相关资源
最近更新 更多