我一直在阅读有关函数指针兼容性的文章,但没有发现以下记录的场景是可接受的(如下所示).

有了这段代码,允许(没有警告)用参数调用函数指针,即使它被定义为具有空参数列表.此外,允许将单个指针作为参数列表的函数(没有警告)分配给类型定义为具有空参数列表的函数指针.并且允许使用参数的后续调用.

由于调用者负责清理堆栈上的参数,我想这是内存安全的,不是吗?只是有些东西让我怀疑它的有效性.

下面的代码是否编译并运行时没有警告,因为它实际上是有效的C语言?这些函数类型真的兼容吗?如果兼容,这是定义的行为吗?

计划

#include <stdlib.h>
#include <stdio.h>

void functionA()
{
    printf("A\n");
}

void functionB(uint8_t* parameter)
{
    printf("B %d\n", *parameter);
}

void (*functionPointer)();

int main()
{
    uint8_t number = 42;

    functionPointer = functionA;
    functionPointer(&number);

    functionPointer = functionB;
    functionPointer(&number);
    
    //functionA(&number); // warning: too many arguments in call

    return 0;
}

输出

% ./a.out
A
B 42

推荐答案

包含其参数类型的函数声明称为prototype(C 2018 6.2.1 2).

有关不带参数声明的函数类型的规则预计将在C2023中更改.预计C2023将从标准中删除非原型声明,并在函数声明中使()相当于(void),因此问题中的代码将是不一致的,因此提出的问题在很大程度上是没有意义的.

这个答案的其余部分涉及C 2018,在这方面与C 1999基本没有变化.

我一直在阅读关于函数指针兼容性的文章,但是没有发现以下场景是可以接受的(下面).

使用没有原型的表达式调用使用原型定义的函数在C 2018 6.5.2.2 6中有所规定.(此处使用的是表达式,而不仅仅是指函数名称,因为可以使用函数名称、指向函数类型的指针的变量或导致指向函数类型的指针的强制转换的表达式来调用函数.)使用具有原型的表达式调用未使用原型定义的函数将在以下段落中指定.

有了这段代码,允许(没有警告)用参数调用函数指针,即使它被定义为具有空参数列表.此外,允许将单个指针作为参数列表的函数(没有警告)分配给类型定义为具有空参数列表的函数指针.并且允许使用参数的后续调用.

非原型函数类型本质上意味着参数没有在类型中指定.关于混合使用非原型函数类型和原型函数类型的规则主要是,只要用于调用函数的参数与函数实际期望的兼容,行为就被定义了.还有一些额外的公差,比如允许intunsigned int传递,前提是该值在两种情况下都可以表示.

由于调用方负责清理堆栈上的参数,我想这是内存安全的,不是吗?

C标准没有说明如何负责清理堆栈上的参数,可以在需要调用来清理参数的平台上实现,以及需要调用函数来清理参数的平台上实现.

在任何函数调用中,编译器都知道从实际参数传递的参数,而不管调用表达式类型是否有原型.因此,编译器可以为调用 routine 生成代码来清理参数,如果平台需要的话.

在一个函数定义中,原型没有...,编译器从原型中知道参数.在没有原型的函数定义中,参数以"标识符列表"样式声明:参数名列在括号内,其声明在函数参数的右括号后面.因此,在这两种情况下,编译器知道参数以及预期的参数,并可以生成代码来清理堆栈(如果平台需要的话).这包括函数定义为()的情况—在函数定义中,这意味着没有参数,因此没有参数.(在非函数定义的函数声明中,()表示未指定参数.

这使得函数定义使用....在这种情况下,编译器通常无法知道参数.此外,调用 routine 不一定知道参数.例如,虽然printf有一个格式字符串指示它期望的参数,但传递的参数比格式字符串调用的多printf个是有效的(C 2018 www.example.com 2).在调用方负责清理堆栈的平台上,这不是问题,因为调用方知道传递了什么参数.在调用函数负责清理堆栈的平台上,这可以通过要求调用 routine 传递参数信息的接口规则来实现.这将是"隐藏"的信息,在参数中不可见.例如,接口可能要求调用 routine 在从函数调用返回时传递要从堆栈中移除的字节数.

下面的代码是否编译并运行时没有警告,因为它实际上是有效的C语言?

更准确地说,它可以在没有警告的情况下编译和执行,因为它符合C代码.("有效"有不同的含义,在C标准中没有定义.程序使用C语言的扩展是有效的.)然而,它并不严格符合C代码.由于第一次使用functionPointer的调用将参数传递给未使用参数定义的函数,因此违反了C 2018 6.5.2.2 6:"…如果参数的数量不等于参数的数量,则行为为未定义的…"这使得行为不是由C标准定义的.

注意使用...和不使用...定义的函数之间的区别.虽然我们可以通过printf个它不知道的参数,但标准并不能保证我们可以通过functionA个参数.原因是...会让编译器注意到,如果平台需要,它必须使用上面讨论的附加参数信息.如果没有...,编译器可能会生成期望有固定数量的参数和固定类型的代码,因此当调用不同数量的参数时,这将是错误的.

这些函数类型真的兼容吗?如果是,这是定义的行为吗?

是的函数类型的兼容性规则在C 2018 www.example.com 15中指定,它们允许原型类型与非原型类型兼容.对于C中的类型,"兼容"主要意味着两个类型可以完成为相同的类型,i.e.,其中指定的任何部分都是相同的.例如,具有大小(数组中元素的数量)的数组类型与不具有大小(未指定数量)的数组类型兼容.类似地,两个函数类型具有相同的返回类型,但不同之处在于一个指定参数类型,另一个不兼容.

C++相关问答推荐

如何在C中通过转换为char * 来访问float的字节表示?

带双指针的2D数组

当包含头文件时,gcc会发出隐式函数声明警告

C strlen on char array

如何避免重新分配指针数组时,我们从一开始就不知道确切的大小

为什么复合文字(C99)的返回会生成更多的汇编代码?

是否可以使用指针算法在不对齐的情况下在 struct 中相同类型的字段的连续序列之间移动?

Flose()在Docker容器中抛出段错误

在每种If-Else情况下执行语句的最佳方式

什么是.c.h文件?

如何用c语言修改shadow文件hash部分(编程)?

如何使用_newindex数组我总是得到错误的参数

用C++高效解析HTTP请求的方法

赋值两侧的后置增量,字符指针

在printf()中用%.*S格式填充长度为0的字符串是否会调用任何UB?如果是,是哪一个?

我编写这段代码是为了判断一个数字是质数、阿姆斯特朗还是完全数,但由于某种原因,当我使用大数时,它不会打印出来

&stdbool.h&q;在嵌入式系统中的使用

Leet代码运行时错误:代码不会在Leet代码上编译,而是在其他编译器中编译,如netbeans和在线编译器

atoi函数最大长-长误差的再创造

使用 SDL2 的 C 程序中的内存泄漏