简体   繁体   中英

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:

#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:

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:

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

but it's hard to say whether it is 100% correct.

A call on a function pointer has to made using the same type as the function type.

In main() the statement 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) . The call is undefined behavior.

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 [...]

The conversion or cast is always safe, when the other type is a function pointer. Any function pointer may be always converted to any another functions 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?

Yes, You can assign a function pointer to any other kind of function pointer:

" 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

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((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."

print_uint of type

"function with struct uint_args_t parameter returning void "

is not compatible to type

"function with struct int_args_t parameter returning void ", which print is declared to point to.

The type of the parameter and the called pointer is different.

The struct ures themselves are not identical nor compatible.


Regarding compatibility:

For two function types to be compatible, both shall specify compatible return types 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. 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 t1 and 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

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.

In this specific case, it's completely safe, because:

  1. int_args_t and uint_args_t are identical, memory layout-wise. And specifically, int and uint are identical (there's no such thing as signed/unsigned registers or memory locations).

  2. Even if 1 wasn't true, the two function definitions have an identical signature -- they receive a pointer and return 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).

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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