为什么这种隐式转换(在不同的指针类型之间)有效?

时间:2014-11-25 13:21:33

标签: c pointers implicit-conversion

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

#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&#34; f称为&#34;。这有效吗?

修改

(以防它不清楚)如果T2是&#34;结构上&#34;该程序仍将compile并运行不同,例如

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

4 个答案:

答案 0 :(得分:11)

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

-std=c99 -pedantic-errors

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

  

错误:传递&#39; f&#39;的参数1来自不兼容的指针类型

由于遗留代码,编译器具有扩展并允许implicit int等功能,因此了解其中的差异非常重要。有关详细信息,请参阅gcc document: Language Standards Supported by GCC

快速查看这实际上无效的方法是转到 Rationale for International Standard—Programming Languages—C,该draft C99 standard会在6.3.2.3 指针部分中告诉我们,该部分处理转化:

  

将指向任何类型的对象的指针转换为a是无效的   指向不同类型的对象而没有显式强制转换的指针。

稍微长一点的路径要求我们转到{{3}}部分6.5.2.2 函数调用,其中写着(强调我的前进):

  

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

然后我们转到6.5.16 分配运算符部分,其中包含:

  

以下其中一项应持有

我们有指针:

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

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

答案 1 :(得分:6)

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

  

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

     

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

     

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

它&#34;有效&#34;因为它们都是指针,因此可以转换,所以它不是一个好主意。

答案 2 :(得分:2)

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

例如,在您的代码中,如果T1T2是具有不同元素的不同结构,并且T2的地址传递给f()并被接受为{ {1}},那绝对错误,结果是致命的。如果它被编译[它应该产生关于T1*]的警告,显然不是意味着它是正确的。

在您的代码中,由于您没有访问passing argument <number> of <a function> from incompatible pointer type内的结构变量,由于编译优化,警告可能已经消失。

它读取

  

如果表示被调用函数的表达式具有类型   确实包含一个原型,参数被隐式转换为   如果通过赋值,对相应参数的类型,采取   每个参数的类型是其不合格的版本   声明的类型。函数原型中的省略号表示法   声明器导致参数类型转换在最后一个之后停止   声明参数。执行默认参数促销   尾随论据。

答案 3 :(得分:1)

正如其他人所提到的,在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的公共布局,而不会在此处执行任何严格别名优化,因此具有强制转换的代码可能是合理地预期“正常工作”,正如您在示例中所看到的那样。