【问题标题】:Why is this implicit conversion (between different pointer types) valid?为什么这种隐式转换(不同指针类型之间)有效?
【发布时间】:2014-11-25 13:21:32
【问题描述】:

我发现自己处于以下情况:

#include <stdio.h>

typedef struct T1 { int id; } T1;  
typedef struct T2 { int id; } T2;

void f(T1 *ptr) { printf("f called\n"); }

int main(void) 
{
    T2 obj; 
    T2 *ptr = &obj; 
    f(ptr); // shouldn't this be a compilation error ? 
    return 0;
}

当然,这是无效的 C++,但在 C 中,程序 prints "f called"。这如何有效?

编辑

(以防万一不清楚)如果T2“结构上”不同,该程序仍将运行compile并运行,例如

typedef struct T2 { double cc[23]; } T2;

【问题讨论】:

  • 如果您使用将警告视为错误的设置进行编译,则代码不会编译 (link)。
  • 有趣。您是否尝试过两种真正不同的结构?
  • 一些编译器只对实际错误发出警告。
  • @gasher729:是的,C 标准允许这样做。对于任何违反语法规则或约束的程序,它只需要至少一次诊断(可以是非致命警告)。编译器要求拒绝翻译单元的唯一情况是当它包含在预处理后仍然存在的#error 指令时。

标签: c pointers implicit-conversion


【解决方案1】:

这是无效的,如果你想强制符合标准的代码,使用正确的标志进行编译很重要,例如gccclang 以下标志:

-std=c99 -pedantic-errors

将生成 C99 标准所需的诊断错误,同样您可以将 -std=c11 用于 C11。这将从gcc (see it live) 生成以下错误:

错误:从不兼容的指针类型传递 'f' 的参数 1

由于遗留代码,编译器具有扩展并允许使用 implicit int 等功能,因此了解其中的区别很重要。详情请见gcc document: Language Standards Supported by GCC

查看这实际上无效的快速方法是转到 Rationale for International Standard—Programming Languages—C,它在6.3.2.3 部分告诉我们指针,它处理以下转换:

将指向任何类型对象的指针转换为 指向不同类型对象的指针,无需显式强制转换。

稍长一点的路径需要我们转到draft C99 standard 部分6.5.2.2 函数调用,它说(强调我的前进):

如果表示被调用函数的表达式有一个类型 确实包含原型,参数被隐式转换,如 如果通过分配

如果我们然后转到6.5.16 赋值运算符部分,它会说:

满足以下条件之一

对于指针,我们有:

  • 两个操作数都是指向兼容类型的合格或不合格版本的指针,左边指向的类型具有所有 右边指向的类型的限定符;
  • 一个操作数是指向对象或不完整类型的指针,另一个是指向 void 的限定或非限定版本的指针,并且 左边指向的类型具有该类型的所有限定符 由右边指向;
  • 左操作数为指针,右操作数为空指针常量;

我们发现这些情况都不成立,因此转换无效。

【讨论】:

  • "将产生违反 C99 标准的错误" -- 几乎,但不完全是。 -pedantic-errors 会将所有标准强制诊断变成错误。其他警告仍然是警告,甚至是恰好指出程序不是有效的 C99 的警告。
【解决方案2】:

编译时,我收到以下警告:

temp.c:在函数'main'中:

temp.c:20:5: 警告:从不兼容的指针类型传递“f”的参数 1 [默认启用]

temp.c:13:6:注意:预期为“struct T1 *”,但参数类型为“struct T2 *”

它是“有效的”,因为它们都是指针,因此可以转换,这不是一个好主意。

【讨论】:

  • 请看我的回答,这是无效的。
  • @ShafikYaghmour 是否在 C99 之前一年在 C 中有效?
  • @Dronz C 标准没有说这是错误的唯一年份是没有 C 标准的年份。在 C89 中同样无效。
  • @Dronz 这在 C89 中也是无效的
  • @ShafikYaghmour 谢谢! :-)
【解决方案3】:

根据 C99 标准,第 6.5.2.2 节 [函数调用] 第 7 段,这是 C 中允许的 valid ,但不是 valid

例如,在你的代码中,如果T1T2是具有不同元素的不同结构,并且T2的地址被传递给f()并被接受为T1*,那么这是绝对错误的结果是致命的。万一它被编译[它应该产生关于passing argument &lt;number&gt; of &lt;a function&gt; from incompatible pointer type的警告],显然并不意味着它是正确的。

在您的代码中,由于您没有访问f() 中的结构变量,由于编译器优化,警告可能已经消失。

上面写着

如果表示被调用函数的表达式有一个类型 确实包含原型,参数被隐式转换,如 如果通过赋值,则对应参数的类型,取 每个参数的类型是其非限定版本 声明的类型。函数原型中的省略号 声明符导致参数类型转换在最后一个之后停止 声明的参数。默认参数提升在 尾随参数。

【讨论】:

  • 您能从规范中添加一些引用的文本吗?我看了草稿,但编号不匹配。
  • @unwind 先生,在我的文档中它在第 72 页。但是,我知识不多,如果我错了,请纠正我。
  • 我根本看不出引用的文本与相关代码有何关系。
  • “好像通过分配”。是什么使相应的分配有效?
  • 该标准没有单独的无效但允许的构造类。
【解决方案4】:

正如其他人所提到的,在 C 中,此代码需要诊断(显然您得到了诊断,以 gcc 警告的形式)。

您可以使用强制转换“修复”代码:

f( (T1 *)ptr );

在您的示例程序中,这很好。但是,在更复杂的程序中会出现问题。由于T1T2 不是兼容的类型,因此f 通过指针写入然后通过ptr 读取(反之亦然)将是严格混叠违规。

不过,您可以解决这个问题,利用联合可用于 C 中的别名这一事实,以及联合成员是具有共同初始序列的结构的特殊规定:

union
{
    T1 t1;
    T2 t2;
} obj;

f( &obj.t1 );                 // might write to ptr->id
printf("%d\n", obj.t2.id);    // OK, writes int that f wrote

由于 union 示例必须工作,任何理智的编译器只会对 T1T2 使用通用布局,而不在这里进行任何严格的别名优化,因此可以合理地预期带有强制转换的代码“只是工作”,正如您在示例运行中看到的那样。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-02-18
    • 1970-01-01
    • 2011-02-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多