我发现自己处于以下情况:
#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;
答案 0 :(得分:11)
这是无效的,如果你想强制使用符合标准的代码,使用正确的标志进行编译是很重要的,例如gcc
和clang
以下标志:
-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中允许的有效,但不是有效。
例如,在您的代码中,如果T1
和T2
是具有不同元素的不同结构,并且T2
的地址传递给f()
并被接受为{ {1}},那绝对错误,结果是致命的。如果它被编译[它应该产生关于T1*
]的警告,显然不是意味着它是正确的。
在您的代码中,由于您没有访问passing argument <number> of <a function> from incompatible pointer type
内的结构变量,由于编译优化,警告可能已经消失。
它读取
如果表示被调用函数的表达式具有类型 确实包含一个原型,参数被隐式转换为 如果通过赋值,对相应参数的类型,采取 每个参数的类型是其不合格的版本 声明的类型。函数原型中的省略号表示法 声明器导致参数类型转换在最后一个之后停止 声明参数。执行默认参数促销 尾随论据。
答案 3 :(得分:1)
正如其他人所提到的,在C中这段代码需要一个诊断(显然你有一个以gcc警告的形式)。
您可以使用强制转换“修复”代码:
f( (T1 *)ptr );
在您的示例程序中,这很好。但是,在更复杂的程序中会出现问题。由于T1
和T2
不是兼容类型,因此让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示例必须工作,任何理智的编译器只会使用T1
和T2
的公共布局,而不会在此处执行任何严格别名优化,因此具有强制转换的代码可能是合理地预期“正常工作”,正如您在示例中所看到的那样。