根据 C 标准,将函数转换为指向各种函数的指针是否合法?

Is it legal to cast a function to a pointer to a function of various kinds per C standard?

我分析了一些用 C 编写的源代码,发现了以下代码片段:

#include <stdio.h>

struct base_args_t {
int a0;
};

struct int_args_t {
struct base_args_t base;
int a1;
};

struct uint_args_t {
struct base_args_t base;
unsigned int a1;
};

void print_int(struct int_args_t *a)
{
    // print int
    printf("%i\n", a->a1);
    return;
}

void print_uint(struct uint_args_t *a)
{
    // print unsigned int
    printf("%u\n", a->a1);
    return;
}

int main()
{
    struct uint_args_t uint_args = {.a1 = 7};
    typedef void (*f_print_type)(struct int_args_t *);
    void (*print)(struct int_args_t *a) = (f_print_type)print_uint;

    print((void *)&uint_args);

    return 0;
}

我想知道是否允许将函数转换为指向各种函数的指针,如示例中所做的那样:

void (*print)(struct int_args_t *a) = (f_print_type)print_uint;

P.S。此外,我已经使用启用的 CFI 消毒剂测试了这个示例,它说:

runtime error: control flow integrity check for type 'void (struct int_args_t *)' failed during indirect function call

但很难说是否100%正确。

对函数指针的调用必须使用与函数类型相同的类型。

main() 中,语句 print(...); 使用 void (*print)(struct int_args_t *a) 调用 print_uint,但 print_uint 的类型为 void print_uint(struct uint_args_t *a)。该调用是未定义的行为。

Is it legal to cast a function to a pointer to a function of various kinds per C standard?

if it is permissible to cast a function to a pointer to a function of various kinds [...]

当另一种类型是函数指针时,转换或强制转换总是安全的。任何函数指针都可以总是转换为任何其他函数指针类型。您必须 调用 具有相同函数指针类型的函数(更准确地说,必须使用 compatible 函数指针调用该函数类型)。

在这种特定情况下,它是完全安全的,因为:

  1. int_args_tuint_args_t 在内存布局方面是相同的。具体来说,intuint 是相同的(没有 signed/unsigned 寄存器或内存位置这样的东西)。

  2. 即使 1 不正确,这两个函数定义也具有相同的签名——它们接收一个指针和 return void。

  3. 函数体在汇编级别上也是相同的,因为您在与接收到的指针的相同偏移量处使用相同的字段,并且您正在使用的字段具有相同的内存布局(如 1).

    中所讨论

如果将其全部剥离为编译器生成的基本汇编代码,则您的代码是绝对安全的。消毒剂告诉您的是,您定义的实际 C 类型不可互换,但这最终并不重要。

Is it legal to cast a pointer to a function to a pointer to a function of various kinds per C standard?

是的,您可以将函数指针分配给任何其他类型的函数指针:

"A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the pointed-to type, the behavior is undefined."

Source: C11, 6.3.2.3/8

所以作业:

void (*print)(struct int_args_t *a) = (f_print_type)print_uint;

正确且合法。


调用未定义行为的是使用指针 print() 来引用调用 print_uint:

print((void *)&uint_args);

因为:

"If a converted pointer is used to call a function whose type is not compatible with the pointed-to type, the behavior is undefined."

print_uint 类型

"具有 struct uint_args_t 参数的函数返回 void"

与类型不兼容

“具有 struct int_args_t 参数返回 void 的函数”,声明 print 指向。

参数类型和调用的指针类型不同

struct确定本身既不相同也不兼容。


关于兼容性:

For two function types to be compatible, both shall specify compatible return types127.

Moreover, the parameter type lists, if both are present, shall agree in the number of parameters and in use of the ellipsis terminator; corresponding parameters shall have compatible types. If one type has a parameter type list and the other type is specified by a function declarator that is not part of a function definition and that contains an empty identifier list, the parameter list shall not have an ellipsis terminator and the type of each parameter shall be compatible with the type that results from the application of the default argument promotions. If one type has a parameter type list and the other type is specified by a function definition that contains a (possibly empty) identifier list, both shall agree in the number of parameters, and the type of each prototype parameter shall be compatible with the type that results from the application of the default argument promotions to the type of the corresponding identifier. (In the determination of type compatibility and of a composite type, each parameter declared with function or array type is taken as having the adjusted type and each parameter declared with qualified type is taken as having the unqualified version of its declared type.)

  1. If both function types are ‘‘old style’’, parameter types are not compared.

Source: C18, §6.7.6.3/15


Two types have compatible type if their types are the same. Additional rules for determining whether two types are compatible are described in 6.7.2 for type specifiers, in 6.7.3 for type qualifiers, and in 6.7.6 for declarators.56)

56)Two types need not be identical to be compatible.

Source: C18, §6.2.7/1


EXAMPLE 2 After the declarations

typedef structs1 { int x; } t1, *tp1;
typedef structs2 { int x; } t2, *tp2;

type t1and the type pointed to by tp1 are compatible. Type t1 is also compatible with type structs1, but not compatible with the types structs2, t2, the type pointed to by tp2, or int.

C18, §6.7.8/5

不同标签的两个结构永远不兼容,即使它们具有相同的成员集和对齐方式,这里也不是这种情况,因为成员 a 的类型在两种结构类型。