我正在学习编程语言原理,并且对C和C ++有疑问。
int (*f)(int);
int (*g)(int);
int (*h)(char);
f = g; // ok
h = g; // warning in C, error in C++
将g
分配到f
(f = g
)中不会导致C或C ++错误,而是将g
分配到h
({{1} })将在C中生成一个编译器警告,并在C ++中生成错误。
我听说h = g
类型通常在C ++中自动广播到char
中,所以我认为这不会引起错误。
有人可以向我解释吗?
答案 0 :(得分:3)
要使两个函数类型兼容,返回类型必须匹配,并且参数的数量和类型必须匹配。在整数类型之间应用的转换规则不适用于函数类型的参数。
C standard的6.7.6.3p15节规定如下:
要使两种功能类型兼容,两者都应指定兼容的返回类型。此外,参数类型列表(如果同时存在)应在参数数量和省略号终止符的使用上达成共识;相应的参数应具有兼容的类型。如果一种类型具有参数类型列表,而另一种类型是由不属于函数定义一部分且包含空标识符列表的函数声明符指定的,则参数列表不应有一个省略号终止符,并且每个参数的类型应与应用默认参数提升的结果类型兼容。如果一种类型具有参数类型列表,而另一种类型由包含(可能为空)标识符列表的函数定义指定,则两者应在参数数量上达成一致,并且每个原型参数的类型均应与该类型兼容这是由于将默认参数提升应用到相应标识符的类型而导致的。 (在确定类型兼容性和复合类型时,将使用函数或数组类型声明的每个参数视为已调整类型,将使用合格类型声明的每个参数视为具有其声明类型的非合格版本。)>
类型int
和char
彼此不兼容,因此具有一个int
参数的函数和具有一个char
参数的另一个函数彼此不兼容,使示例中的分配无效。
C编译器通常会发出警告。在您的示例中,尝试通过调用函数来延迟h
将会调用undefined behavior。
答案 1 :(得分:3)
将
g
分配到f
(f = g
)中不会导致C / C ++错误,而是将g
分配到h
({{1} })将在C中生成一个编译器警告,并在C ++中生成错误。
这是不正确的。尝试执行h = g
的程序无效,可能是C或C ++。但是,C标准明确提到允许C编译器成功编译无效程序,只要它针对每次违反某些规则的情况发出诊断消息。您得到警告,然后C编译器继续执行。
实际上,C ++标准包含非常相似的措辞:
如果在实现不支持该结构的情况下某个程序违反了任何可诊断的规则或出现了本文档中描述为“有条件支持”的结构,则合格的实现应发出至少一条诊断消息。 / p>
[...]
一个符合标准的实现可以具有扩展(包括附加的库函数),前提是它们不会改变任何格式正确的程序的行为。需要执行一些诊断程序才能使用根据本文档格式错误的扩展名。 但是,这样做后,他们可以编译和执行此类程序。
但是,在涉及可诊断的违规时,通常C ++编译器默认情况下更严格 ,并且默认情况下编译会失败,而同一供应商的C编译器会 >成功编译了这样的翻译单元。
请注意,C和C ++都规定您可以使用 cast (即h = g
)进行转换。但是您必须再次通过h = (int (*)(char))g
调用一个函数,而不必先将其转换回h
!
答案 2 :(得分:1)
定义函数的指针时,实质上是定义新的变量类型。
因此,在您的示例中,f
和g
是同一类型,因为它们的定义是相同的,而h
是另一种类型,因为它们的定义是不同的。
尽管C和C ++编译器都知道如何在char
和int
类型之间进行转换,因为它们是语言的内置类型,但它们却不知道如何在g
之间进行转换和h
类型,因为您已经定义了这些类型。
char
是g
定义的一部分这一事实在这里无济于事,因为编译器无权更改您定义的内部。
请考虑可能导致的错误类型: 如果使用所有参数都放在堆栈上的调用约定,则向期望int的函数发送char会导致它获得错误的值,因为它将从堆栈中读取不属于send的额外字节值。
答案 3 :(得分:1)
您可能还想知道为什么这些函数指针不能隐式地相互转换。考虑以下函数定义:
void f(void(*f)(char))
{
f('A');
}
编译器现在需要为此功能生成机器码。就我而言,是:
mov rax, rdi
mov edi, 65
jmp rax
如果将f
作为参数传递给具有不同类型参数的函数的指针,则该机器代码通常将不起作用。这里,f
的参数通过edi
寄存器传递。碰巧的是,对于我的实现,该方法对于参数类型为int
的函数也适用。但这纯粹是一个实现问题,标准规则可能不受它驱动。
为说明起见,如果我将参数类型为f
的函数的指针作为long
的参数传递,则它将停止工作,因为那样的话该参数就不会通过f
寄存器传递到rdi
(如该函数所期望的那样)。