【问题标题】:Is casting a function pointer that takes a const pointer argument to the equivalent function but with non constant pointer arguments OK?将一个函数指针转换为等效函数,该函数指针接受一个 const 指针参数,但具有非常量指针参数可以吗?
【发布时间】:2021-12-08 10:54:56
【问题描述】:

我创建了 2 个函数,用于读取和写入路径,声明如下:

int Read(const char * /*Filename*/, void * /*Ptr*/, size_t /*Size*/), Write(const char * /*Filename*/, const void * /*Ptr*/, size_t /*Size*/);

我创建了一个附加函数,它将使用路径调用上述函数之一

static int IOData(int(*const Func)(const char *, void *, size_t)) {
    char Filename[DATA_PATH_LEN];
    // Build path
    return Func(Filename, &Data, sizeof(Data));
}

但是,当 Write 作为回调传递给 IOData 时,编译器会引发以下警告

不兼容的指针类型将'int (const char *, const void , int)'传递给'int ()(const char *, void *, int)'类型的参数

将接受 const 指针的函数强制转换为接受非 const 指针的函数是未定义的行为吗?

我注意到有一个almost identical question,但该问题使用 C++,但该问题使用纯 C,因此不能使用模板

【问题讨论】:

    标签: c function pointers function-pointers undefined-behavior


    【解决方案1】:

    这是不允许的,因为相应参数之一的类型不兼容。

    兼容的类型在C standard的第6.2.7p1节中定义:

    如果它们的类型相同,则两种类型具有兼容的类型。额外的 描述了确定两种类型是否兼容的规则 在 6.7.2 中用于类型说明符,在 6.7.3 中用于类型限定符,在 6.7.6 对于声明符。 ...

    第 6.7.3p10 节详细介绍了合格类型的兼容性:

    为了使两个合格的类型兼容,两者都应具有 兼容类型的相同限定版本;类型顺序 说明符或限定符列表中的限定符不影响 指定的类型。

    这意味着const void *void * 不兼容。

    函数类型的兼容性在第 6.7.6.3p15 节中描述:

    对于要兼容的两个函数类型,两者都应指定 compatible 返回类型。 此外,参数类型列表,如果两者都是 目前,应同意参数的数量和使用 省略号终止符;相应的参数应具有兼容的 types. 如果一种类型有参数类型列表,而另一种类型是 由不属于函数的函数声明符指定 定义并包含一个空标识符列表,参数 列表不应有省略号终止符和每个的类型 参数应与产生的类型兼容 应用默认参数提升。如果一种类型有 参数类型列表,其他类型由函数指定 包含(可能为空的)标识符列表的定义,两者 参数的数量和每个参数的类型应一致 原型参数应与产生的类型兼容 从应用默认参数提升到类型 对应的标识符。 (在确定类型 兼容性和复合类型,每个参数声明 函数或数组类型被视为具有调整后的类型,并且每个 使用限定类型声明的参数被视为具有 其声明类型的非限定版本。)

    所以因为一组对应的参数不兼容,所以函数类型不兼容。

    最后,关于函数调用运算符() 的第 6.5.2.2p9 节描述了在这种情况下会发生什么:

    如果函数定义的类型与 表示表达式所指向的(表达式的)类型 调用函数,行为未定义。

    所以通过不兼容的函数指针类型调用函数会触发未定义的行为,因此不应该这样做。

    【讨论】:

    • 我应该怎么做?
    • @user16217248 传递一个指定读/写的标志,并直接调用相应的函数。
    • 如果一个实现的配置方式保证所有调用都以与平台 ABI 一致的方式进行,并且平台 ABI 不区分 const 和非 const 指针,这些因素加在一起意味着实现,作为“符合语言扩展”的一种形式,将有意义地处理代码。大多数 ABI 都会有适当的保证,并且几乎所有实现都可以配置为根据 ABI 处理函数调用,至少如果它们在编译单元之间进行。
    【解决方案2】:

    该标准试图将其行为在某些看似合理的实现上定义为不切实际的任何行为归类为未定义行为。因为将动作分类为 UB 绝不会损害质量实现的能力,即作为“符合语言扩展”,在存在时以有用的普通方式处理动作,因此没有必要避免将其表征为 UB 动作大多数实现都会以同样有用的方式处理。

    尝试静态确定最大堆栈使用量的实现可能会合理地假设对具有特定签名的函数指针的调用将仅调用其地址已被获取且其签名完全匹配的函数。如果标准要求指向此类函数的指针是可互换的,那可能会不可挽回地破坏静态分析工具以前能够适应的程序。

    没有理由期望质量实现不应该可配置以将此类函数指针视为可互换的,如果这样做会有用且实用,但标准放弃了对实用性和实现质量问题的管辖权实用性。不幸的是,很难知道应该依赖哪些实现来支持这样的构造,因为许多没有理由不支持这样的构造的实现并不认为它们支持它们的事实足够值得注意以证明明确的合理性文档。

    【讨论】:

      猜你喜欢
      • 2012-01-04
      • 1970-01-01
      • 2011-02-21
      • 2013-06-06
      • 1970-01-01
      • 1970-01-01
      • 2020-01-12
      • 1970-01-01
      • 2018-09-02
      相关资源
      最近更新 更多