简体   繁体   English

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

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

I analyzed some source code written in C and found the following code snippet:我分析了一些用 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;
}

I am wondering if it is permissible to cast a function to a pointer to a function of various kinds, as is done in the example:我想知道是否允许将 function 转换为指向各种 function 的指针,如示例中所做的那样:

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

PS Moreover, I've tested this example with enabled CFI sanitizer and it says: PS 此外,我已经使用启用的 CFI sanitizer 测试了这个示例,它说:

runtime error: control flow integrity check for type 'void (struct int_args_t *)' failed during indirect function call运行时错误:间接 function 调用期间类型“void (struct int_args_t *)”的控制流完整性检查失败

but it's hard to say whether it is 100% correct.但很难说它是否 100% 正确。

A call on a function pointer has to made using the same type as the function type.对 function 指针的调用必须使用与 function 类型相同的类型。

In main() the statement print(...);main()语句中print(...); calls print_uint using void (*print)(struct int_args_t *a) but print_uint is of type void print_uint(struct uint_args_t *a) .使用void (*print)(struct int_args_t *a)调用print_uintprint_uint的类型为void print_uint(struct uint_args_t *a) The call is undefined behavior.调用是未定义的行为。

Is it legal to cast a function to a pointer to a function of various kinds per C standard?根据 C 标准将 function 转换为指向各种 function 的指针是否合法?

if it is permissible to cast a function to a pointer to a function of various kinds [...]如果允许将 function 转换为指向各种 function 的指针 [...]

The conversion or cast is always safe, when the other type is a function pointer.当另一种类型是 function 指针时,转换或强制转换始终是安全的。 Any function pointer may be always converted to any another functions pointer type.任何 function 指针始终可以转换为任何其他函数指针类型。 You have to call the function with the same function pointer type as it is (more exact, the function has to be called with a compatible function pointer type). You have to call the function with the same function pointer type as it is (more exact, the function has to be called with a compatible function pointer type).

Is it legal to cast a pointer to a function to a pointer to a function of various kinds per C standard?根据 C 标准将指向 function 的指针转换为指向各种 function 的指针是否合法?

Yes, You can assign a function pointer to any other kind of function pointer:是的,您可以将 function 指针分配给任何其他类型的 function 指针:

" 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." "指向一种类型的 function 的指针可以转换为指向另一种类型的 function 的指针,然后再返回;结果应与原始指针比较。如果使用转换后的指针调用类型不兼容的 ZC1C425268E68384F11 的 ZC1C425268E68384D1对于指向的类型,行为是未定义的。”

Source: C11, 6.3.2.3/8来源:C11,6.3.2.3/8

So the assignment:所以任务:

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

is correct and legal.是正确和合法的。


What invokes undefined behavior is using the pointer print() to refer to call print_uint :调用未定义行为的是使用指针print()来引用调用print_uint

print((void *)&uint_args);

because:因为:

"If a converted pointer is used to call a function whose type is not compatible with the pointed-to type, the behavior is undefined." “如果使用转换后的指针调用类型与指向类型不兼容的 function,则行为未定义。”

print_uint of type print_uint类型

"function with struct uint_args_t parameter returning void " “带有struct uint_args_t参数的函数返回void

is not compatible to type与类型不兼容

"function with struct int_args_t parameter returning void ", which print is declared to point to. “带有struct int_args_t参数返回void的函数”, print被声明指向。

The type of the parameter and the called pointer is different.参数的类型和被调用的指针不同。

The struct ures themselves are not identical nor compatible.这些struct本身并不相同也不兼容。


Regarding compatibility:关于兼容性:

For two function types to be compatible, both shall specify compatible return types 127 .对于两个要兼容的 function 类型,两者都应指定兼容的返回类型127

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.如果一种类型具有参数类型列表,而另一种类型由 function 定义的一部分且包含空标识符列表的 function 声明符指定,则参数列表不应有省略号终止符,并且每个参数的类型应与应用默认参数提升所产生的类型兼容。 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.如果一种类型具有参数类型列表,而另一种类型由包含(可能为空)标识符列表的 function 定义指定,则两者在参数数量上应一致,并且每个原型参数的类型应与类型兼容这是由于将默认参数提升应用于相应标识符的类型而产生的。 (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.) (在确定类型兼容性和复合类型时,每个用 function 或数组类型声明的参数都被认为具有调整后的类型,每个用限定类型声明的参数都被认为具有其声明类型的非限定版本。)

  1. If both function types are ''old style'', parameter types are not compared.如果 function 类型都是“旧式”,则不比较参数类型。

Source: C18, §6.7.6.3/15资料来源: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)用于确定两种类型是否兼容的附加规则在 6.7.2 中描述了类型说明符,在 6.7.3 中描述了类型限定符,在 6.7.6 中描述了声明符。56)

56)Two types need not be identical to be compatible. 56) 两种类型不必相同才能兼容。

Source: C18, §6.2.7/1资料来源:C18,§6.2.7/1


EXAMPLE 2 After the declarations示例 2在声明之后

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

type t1 and the type pointed to by tp1 are compatible.类型t1tp1指向的类型是兼容的。 Type t1 is also compatible with type structs1 , but not compatible with the types structs2 , t2 , the type pointed to by tp2 , or int .类型t1也与类型structs1兼容,但与类型structs2t2tp2指向的类型或int不兼容

C18, §6.7.8/5 C18,§6.7.8/5

Two structures of different tags are never compatible, even if they would have the same set of members and alignment, which isn't the case here too since the type of the member a is different between the two structure types.不同标签的两个结构永远不会兼容,即使它们具有相同的成员集和 alignment,但这里也不是这种情况,因为成员a的类型在两种结构类型之间是不同的。

In this specific case, it's completely safe, because:在这种特定情况下,它是完全安全的,因为:

  1. int_args_t and uint_args_t are identical, memory layout-wise. int_args_tuint_args_t是相同的,memory 布局方式。 And specifically, int and uint are identical (there's no such thing as signed/unsigned registers or memory locations).具体来说, intuint是相同的(没有有符号/无符号寄存器或 memory 位置之类的东西)。

  2. Even if 1 wasn't true, the two function definitions have an identical signature -- they receive a pointer and return void.即使 1 不正确,两个 function 定义也具有相同的签名——它们接收一个指针并返回 void。

  3. The function bodies are also identical on an assembly level, since you use the same field at the same offset from the pointer you receive, and the field you're using has the same memory layout (as discussed in 1). function 主体在装配级别上也是相同的,因为您使用与收到的指针相同的偏移量的相同字段,并且您使用的字段具有相同的 memory 布局(如 1 中所述)。

If you strip it all down to the basic assembly code generated by the compiler, your code is perfectly safe.如果您将其全部剥离为编译器生成的基本汇编代码,那么您的代码是完全安全的。 What the sanitizer is telling you is that the actual C types you defined aren't as interchangeable, but that doesn't matter in the end.消毒剂告诉您的是,您定义的实际 C 类型不能互换,但这最终无关紧要。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM