将函数指针转换为另一种类型

时间:2022-07-06 16:04:23

Let's say I have a function that accepts a void (*)(void*) function pointer for use as a callback:

假设我有一个函数,它接受一个void (*)(void*)函数指针作为回调:

void do_stuff(void (*callback_fp)(void*), void* callback_arg);

Now, if I have a function like this:

现在,如果我有一个像这样的函数

void my_callback_function(struct my_struct* arg);

Can I do this safely?

我能安全地做这件事吗?

do_stuff((void (*)(void*)) &my_callback_function, NULL);

I've looked at this question and I've looked at some C standards which say you can cast to 'compatible function pointers', but I cannot find a definition of what 'compatible function pointer' means.

我看过这个问题,也看过一些C标准说你可以转换成“兼容函数指针”,但是我找不到“兼容函数指针”的定义。

7 个解决方案

#1


101  

As far as the C standard is concerned, if you cast a function pointer to a function pointer of a different type and then call that, it is undefined behavior. See Annex J.2 (informative):

就C标准而言,如果您将一个函数指针转换为另一个类型的函数指针,然后调用它,那么它就是未定义的行为。见附件J.2(信息):

The behavior is undefined in the following circumstances:

行为在以下情况下不明确:

  • A pointer is used to call a function whose type is not compatible with the pointed-to type (6.3.2.3).
  • 指针用于调用类型与指向类型不兼容的函数(6.3.2.3)。

Section 6.3.2.3, paragraph 8 reads:

第6.3.2.3节第8段如下:

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.

指向一个类型函数的指针可以转换为指向另一个类型的函数的指针;结果应与原始指针进行比较。如果使用转换指针来调用类型与指向类型不兼容的函数,则该行为是未定义的。

So in other words, you can cast a function pointer to a different function pointer type, cast it back again, and call it, and things will work.

换句话说,你可以将一个函数指针转换成另一种函数指针类型,再把它转换回来,然后调用它,这样就可以了。

The definition of compatible is somewhat complicated. It can be found in section 6.7.5.3, paragraph 15:

兼容的定义有点复杂。见第6.7.5.3节第15段:

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

对于两个要兼容的函数类型,都应该指定兼容的返回类型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.)

此外,如果存在参数类型列表,则在参数的数量和使用省略号终止符时应一致;相应的参数应具有兼容的类型。如果一种类型指定参数类型列表和其他类型的函数说明符,不是一个函数定义的一部分,包含一个空的标识符列表,参数列表不得有省略号终结者和每个参数的类型兼容的类型,结果从应用程序默认参数促销。如果一种类型指定参数类型列表和其他类型的函数定义包含一个标识符列表(可能为空),都应当同意参数的数量,每个原型参数的类型应当兼容类型,结果从默认参数的应用推广到相应的类型标识符。(在确定类型兼容性和复合类型时,将使用函数或数组类型声明的每个参数作为调整类型,并将声明为合格类型的每个参数作为声明类型的不合格版本。)

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

如果两个函数类型都是“旧样式”,则不比较参数类型。

The rules for determining whether two types are compatible are described in section 6.2.7, and I won't quote them here since they're rather lengthy, but you can read them on the draft of the C99 standard (PDF).

第6.2.7节描述了确定两种类型是否兼容的规则,这里我不会引用它们,因为它们相当长,但是您可以在C99标准草案中阅读它们(PDF)。

The relevant rule here is in section 6.7.5.1, paragraph 2:

有关规则见第6.7.5.1节第2段:

For two pointer types to be compatible, both shall be identically qualified and both shall be pointers to compatible types.

对于两个可兼容的指针类型,两者都应具有相同的限定条件,并且两者都是兼容类型的指针。

Hence, since a void* is not compatible with a struct my_struct*, a function pointer of type void (*)(void*) is not compatible with a function pointer of type void (*)(struct my_struct*), so this casting of function pointers is technically undefined behavior.

因此,由于void*与struct my_struct*不兼容,所以void (*)(void*)(*)类型的函数指针与void (*)(*)(struct my_struct*)类型的函数指针不兼容,因此从技术上讲,函数指针的转换是未定义的行为。

In practice, though, you can safely get away with casting function pointers in some cases. In the x86 calling convention, arguments are pushed on the stack, and all pointers are the same size (4 bytes in x86 or 8 bytes in x86_64). Calling a function pointer boils down to pushing the arguments on the stack and doing an indirect jump to the function pointer target, and there's obviously no notion of types at the machine code level.

但是在实践中,在某些情况下,您可以安全地使用转换函数指针。在x86调用约定中,在堆栈上推入参数,所有指针都是相同的大小(x86中的4字节,x86_64中的8字节)。调用函数指针可归结为在堆栈上推入参数并间接跳转到函数指针目标,显然在机器代码级别不存在类型的概念。

Things you definitely can't do:

你绝对不能做的事情:

  • Cast between function pointers of different calling conventions. You will mess up the stack and at best, crash, at worst, succeed silently with a huge gaping security hole. In Windows programming, you often pass function pointers around. Win32 expects all callback functions to use the stdcall calling convention (which the macros CALLBACK, PASCAL, and WINAPI all expand to). If you pass a function pointer that uses the standard C calling convention (cdecl), badness will result.
  • 在不同调用约定的函数指针之间进行转换。你会把堆栈弄得一团糟,最坏的情况是,在最坏的情况下,以一个巨大的安全漏洞悄无声息地成功。在Windows编程中,经常会传递函数指针。Win32期望所有回调函数都使用stdcall调用约定(宏回调、PASCAL和WINAPI都扩展到)。如果您传递的函数指针使用了标准的C调用约定(cdecl),那么就会产生不好的结果。
  • In C++, cast between class member function pointers and regular function pointers. This often trips up C++ newbies. Class member functions have a hidden this parameter, and if you cast a member function to a regular function, there's no this object to use, and again, much badness will result.
  • 在c++中,在类成员函数指针和常规函数指针之间转换。这经常会让c++新手感到困惑。类成员函数有一个隐藏的参数,如果您将一个成员函数转换为一个常规函数,那么就不会使用这个对象,同样,会产生很多不好的结果。

Another bad idea that might sometimes work but is also undefined behavior:

另一个不好的想法,有时会起作用,但也是未定义的行为:

  • Casting between function pointers and regular pointers (e.g. casting a void (*)(void) to a void*). Function pointers aren't necessarily the same size as regular pointers, since on some architectures they might contain extra contextual information. This will probably work ok on x86, but remember that it's undefined behavior.
  • 函数指针和常规指针之间的转换(例如,将一个void (*)(void)转换为一个void*))。函数指针不一定与常规指针相同大小,因为在某些体系结构中,它们可能包含额外的上下文信息。这在x86上可能没问题,但请记住这是未定义的行为。

#2


25  

I asked about this exact same issue regarding some code in GLib recently. (GLib is a core library for the GNOME project and written in C.) I was told the entire slots'n'signals framework depends upon it.

最近我在GLib中问过同样的问题。(GLib是GNOME项目的核心库,用c编写)我被告知整个插槽和信号框架都依赖于它。

Throughout the code, there are numerous instances of casting from type (1) to (2):

在整个代码中,从type(1)到(2)的转换有很多实例:

  1. typedef int (*CompareFunc) (const void *a, const void *b)
  2. typedef int (*CompareFunc) (const void *a, const void *b)
  3. typedef int (*CompareDataFunc) (const void *b, const void *b, void *user_data)
  4. typedef int (*CompareDataFunc) (const void *b, const void *b, void *user_data)

It is common to chain-thru with calls like this:

这样的电话是很常见的:

int stuff_equal (GStuff      *a,
                 GStuff      *b,
                 CompareFunc  compare_func)
{
    return stuff_equal_with_data(a, b, (CompareDataFunc) compare_func, NULL);
}

int stuff_equal_with_data (GStuff          *a,
                           GStuff          *b,
                           CompareDataFunc  compare_func,
                           void            *user_data)
{
    int result;
    /* do some work here */
    result = compare_func (data1, data2, user_data);
    return result;
}

See for yourself here in g_array_sort(): http://git.gnome.org/browse/glib/tree/glib/garray.c

您可以在g_array_sort()中看到自己:http://git.gnome.org/browse/glib/tree/glib/garray.c。

The answers above are detailed and likely correct -- if you sit on the standards committee. Adam and Johannes deserve credit for their well-researched responses. However, out in the wild, you will find this code works just fine. Controversial? Yes. Consider this: GLib compiles/works/tests on a large number of platforms (Linux/Solaris/Windows/OS X) with a wide variety of compilers/linkers/kernel loaders (GCC/CLang/MSVC). Standards be damned, I guess.

如果你是标准委员会的成员,上面的答案是详细的,而且很可能是正确的。亚当和约翰尼斯的回答经过充分研究,值得称赞。然而,在野外,您会发现这段代码工作得很好。有争议的吗?是的。考虑一下这一点:GLib在大量平台(Linux/Solaris/Windows/OS X)上编译/工作/测试,使用各种编译器/链接器/内核加载器(GCC/CLang/MSVC)。我想标准是该死的。

I spent some time thinking about these answers. Here is my conclusion:

我花了一些时间思考这些答案。这是我的结论:

  1. If you are writing a callback library, this might be OK. Caveat emptor -- use at your own risk.
  2. 如果您正在编写回调库,这可能是可以的。买者自负——风险自负。
  3. Else, don't do it.
  4. 别的,不要这样做。

Thinking deeper after writing this response, I would not be surprised if the code for C compilers uses this same trick. And since (most/all?) modern C compilers are bootstrapped, this would imply the trick is safe.

在编写完这个响应之后,如果C编译器的代码使用同样的技巧,我不会感到惊讶。由于(大多数/所有)现代C编译器都是自启动的,这意味着这个技巧是安全的。

A more important question to research: Can someone find a platform/compiler/linker/loader where this trick does not work? Major brownie points for that one. I bet there are some embedded processors/systems that don't like it. However, for desktop computing (and probably mobile/tablet), this trick probably still works.

一个更重要的问题是:有人能找到一个平台/编译器/链接器/加载器吗?布朗尼少校为那一个点了分。我敢打赌有些嵌入式处理器/系统不喜欢它。然而,对于桌面计算(可能是移动/平板电脑),这个技巧可能仍然有效。

#3


7  

The point really isn't whether you can. The trivial solution is

关键不是你能否做到。简单的解决方案是

void my_callback_function(struct my_struct* arg);
void my_callback_helper(void* pv)
{
    my_callback_function((struct my_struct*)pv);
}
do_stuff(&my_callback_helper);

A good compiler will only generate code for my_callback_helper if it's really needed, in which case you'd be glad it did.

一个好的编译器只会为my_callback_helper生成代码,如果真的需要的话,在这种情况下,你会很高兴的。

#4


4  

You have a compatible function type if the return type and parameter types are compatible - basically (it's more complicated in reality :)). Compatibility is the same as "same type" just more lax to allow to have different types but still have some form of saying "these types are almost the same". In C89, for example, two structs were compatible if they were otherwise identical but just their name was different. C99 seem to have changed that. Quoting from the c rationale document (highly recommended reading, btw!):

如果返回类型和参数类型是兼容的,那么您就有了兼容的函数类型——基本上(实际上更复杂)。兼容性和“相同类型”是一样的,只是允许有不同的类型,但仍然有某种形式说“这些类型几乎是相同的”。例如,在C89中,如果两个结构体在其他方面是相同的,但是它们的名称不同,那么它们是兼容的。C99似乎改变了这一点。引用c基本原理文档(强烈推荐阅读,顺便说一句!):

Structure, union, or enumeration type declarations in two different translation units do not formally declare the same type, even if the text of these declarations come from the same include file, since the translation units are themselves disjoint. The Standard thus specifies additional compatibility rules for such types, so that if two such declarations are sufficiently similar they are compatible.

两个不同翻译单元中的结构、联合或枚举类型声明不会正式声明相同的类型,即使这些声明的文本来自相同的include文件,因为翻译单元本身是不相交的。因此,标准为此类类型指定了附加的兼容性规则,因此,如果两个这样的声明足够相似,它们是兼容的。

That said - yeah strictly this is undefined behavior, because your do_stuff function or someone else will call your function with a function pointer having void* as parameter, but your function has an incompatible parameter. But nevertheless, i expect all compilers to compile and run it without moaning. But you can do cleaner by having another function taking a void* (and registering that as callback function) which will just call your actual function then.

这就是说,严格来说这是一个未定义的行为,因为你的do_stuff函数或者其他人会用一个函数指针来调用你的函数,这个指针有void*作为参数,但是你的函数有一个不兼容的参数。但是,尽管如此,我还是希望所有编译器在编译和运行时都不发出呻吟。但是您可以通过另一个函数获取void*(并将其注册为callback函数)来实现clean,这个函数将调用您的实际函数。

#5


2  

As C code compiles to instruction which do not care at all about pointer types, it's quite fine to use the code you mention. You'd run into problems when you'd run do_stuff with your callback function and pointer to something else then my_struct structure as argument.

由于C代码编译为指令,根本不关心指针类型,因此使用您提到的代码是很好的。当你用回调函数和指向其他东西的指针运行do_stuff时,你会遇到问题,然后my_struct结构作为参数。

I hope I can make it clearer by showing what would not work:

我希望我能通过展示什么是无效的来说明:

int my_number = 14;
do_stuff((void (*)(void*)) &my_callback_function, &my_number);
// my_callback_function will try to access int as struct my_struct
// and go nuts

or...

还是……

void another_callback_function(struct my_struct* arg, int arg2) { something }
do_stuff((void (*)(void*)) &another_callback_function, NULL);
// another_callback_function will look for non-existing second argument
// on the stack and go nuts

Basically, you can cast pointers to whatever you like, as long as the data continue to make sense at run-time.

基本上,只要数据在运行时继续有意义,您就可以对您喜欢的任何东西投下指针。

#6


0  

If you think about the way function calls work in C/C++, they push certain items on the stack, jump to the new code location, execute, then pop the stack on return. If your function pointers describe functions with the same return type and the same number/size of arguments, you should be okay.

如果您考虑函数调用在C/ c++中的工作方式,它们会在堆栈上推入某些项,跳到新的代码位置,执行,然后返回时弹出堆栈。如果函数指针描述的函数具有相同的返回类型和相同数量/大小的参数,那么应该没问题。

Thus, I think you should be able to do so safely.

因此,我认为你应该能够安全地这样做。

#7


0  

Void pointers are compatible with other types of pointer. It's the backbone of how malloc and the mem functions (memcpy, memcmp) work. Typically, in C (Rather than C++) NULL is a macro defined as ((void *)0).

空指针与其他类型的指针兼容。它是malloc和mem函数(memcpy, memcmp)工作的主干。通常,在C(而不是c++)中,NULL是定义为((void *)0的宏。

Look at 6.3.2.3 (Item 1) in C99:

查看C99中的6.3.2.3(项目1):

A pointer to void may be converted to or from a pointer to any incomplete or object type

指向void的指针可以转换为或从指向任何不完整或对象类型的指针

#1


101  

As far as the C standard is concerned, if you cast a function pointer to a function pointer of a different type and then call that, it is undefined behavior. See Annex J.2 (informative):

就C标准而言,如果您将一个函数指针转换为另一个类型的函数指针,然后调用它,那么它就是未定义的行为。见附件J.2(信息):

The behavior is undefined in the following circumstances:

行为在以下情况下不明确:

  • A pointer is used to call a function whose type is not compatible with the pointed-to type (6.3.2.3).
  • 指针用于调用类型与指向类型不兼容的函数(6.3.2.3)。

Section 6.3.2.3, paragraph 8 reads:

第6.3.2.3节第8段如下:

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.

指向一个类型函数的指针可以转换为指向另一个类型的函数的指针;结果应与原始指针进行比较。如果使用转换指针来调用类型与指向类型不兼容的函数,则该行为是未定义的。

So in other words, you can cast a function pointer to a different function pointer type, cast it back again, and call it, and things will work.

换句话说,你可以将一个函数指针转换成另一种函数指针类型,再把它转换回来,然后调用它,这样就可以了。

The definition of compatible is somewhat complicated. It can be found in section 6.7.5.3, paragraph 15:

兼容的定义有点复杂。见第6.7.5.3节第15段:

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

对于两个要兼容的函数类型,都应该指定兼容的返回类型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.)

此外,如果存在参数类型列表,则在参数的数量和使用省略号终止符时应一致;相应的参数应具有兼容的类型。如果一种类型指定参数类型列表和其他类型的函数说明符,不是一个函数定义的一部分,包含一个空的标识符列表,参数列表不得有省略号终结者和每个参数的类型兼容的类型,结果从应用程序默认参数促销。如果一种类型指定参数类型列表和其他类型的函数定义包含一个标识符列表(可能为空),都应当同意参数的数量,每个原型参数的类型应当兼容类型,结果从默认参数的应用推广到相应的类型标识符。(在确定类型兼容性和复合类型时,将使用函数或数组类型声明的每个参数作为调整类型,并将声明为合格类型的每个参数作为声明类型的不合格版本。)

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

如果两个函数类型都是“旧样式”,则不比较参数类型。

The rules for determining whether two types are compatible are described in section 6.2.7, and I won't quote them here since they're rather lengthy, but you can read them on the draft of the C99 standard (PDF).

第6.2.7节描述了确定两种类型是否兼容的规则,这里我不会引用它们,因为它们相当长,但是您可以在C99标准草案中阅读它们(PDF)。

The relevant rule here is in section 6.7.5.1, paragraph 2:

有关规则见第6.7.5.1节第2段:

For two pointer types to be compatible, both shall be identically qualified and both shall be pointers to compatible types.

对于两个可兼容的指针类型,两者都应具有相同的限定条件,并且两者都是兼容类型的指针。

Hence, since a void* is not compatible with a struct my_struct*, a function pointer of type void (*)(void*) is not compatible with a function pointer of type void (*)(struct my_struct*), so this casting of function pointers is technically undefined behavior.

因此,由于void*与struct my_struct*不兼容,所以void (*)(void*)(*)类型的函数指针与void (*)(*)(struct my_struct*)类型的函数指针不兼容,因此从技术上讲,函数指针的转换是未定义的行为。

In practice, though, you can safely get away with casting function pointers in some cases. In the x86 calling convention, arguments are pushed on the stack, and all pointers are the same size (4 bytes in x86 or 8 bytes in x86_64). Calling a function pointer boils down to pushing the arguments on the stack and doing an indirect jump to the function pointer target, and there's obviously no notion of types at the machine code level.

但是在实践中,在某些情况下,您可以安全地使用转换函数指针。在x86调用约定中,在堆栈上推入参数,所有指针都是相同的大小(x86中的4字节,x86_64中的8字节)。调用函数指针可归结为在堆栈上推入参数并间接跳转到函数指针目标,显然在机器代码级别不存在类型的概念。

Things you definitely can't do:

你绝对不能做的事情:

  • Cast between function pointers of different calling conventions. You will mess up the stack and at best, crash, at worst, succeed silently with a huge gaping security hole. In Windows programming, you often pass function pointers around. Win32 expects all callback functions to use the stdcall calling convention (which the macros CALLBACK, PASCAL, and WINAPI all expand to). If you pass a function pointer that uses the standard C calling convention (cdecl), badness will result.
  • 在不同调用约定的函数指针之间进行转换。你会把堆栈弄得一团糟,最坏的情况是,在最坏的情况下,以一个巨大的安全漏洞悄无声息地成功。在Windows编程中,经常会传递函数指针。Win32期望所有回调函数都使用stdcall调用约定(宏回调、PASCAL和WINAPI都扩展到)。如果您传递的函数指针使用了标准的C调用约定(cdecl),那么就会产生不好的结果。
  • In C++, cast between class member function pointers and regular function pointers. This often trips up C++ newbies. Class member functions have a hidden this parameter, and if you cast a member function to a regular function, there's no this object to use, and again, much badness will result.
  • 在c++中,在类成员函数指针和常规函数指针之间转换。这经常会让c++新手感到困惑。类成员函数有一个隐藏的参数,如果您将一个成员函数转换为一个常规函数,那么就不会使用这个对象,同样,会产生很多不好的结果。

Another bad idea that might sometimes work but is also undefined behavior:

另一个不好的想法,有时会起作用,但也是未定义的行为:

  • Casting between function pointers and regular pointers (e.g. casting a void (*)(void) to a void*). Function pointers aren't necessarily the same size as regular pointers, since on some architectures they might contain extra contextual information. This will probably work ok on x86, but remember that it's undefined behavior.
  • 函数指针和常规指针之间的转换(例如,将一个void (*)(void)转换为一个void*))。函数指针不一定与常规指针相同大小,因为在某些体系结构中,它们可能包含额外的上下文信息。这在x86上可能没问题,但请记住这是未定义的行为。

#2


25  

I asked about this exact same issue regarding some code in GLib recently. (GLib is a core library for the GNOME project and written in C.) I was told the entire slots'n'signals framework depends upon it.

最近我在GLib中问过同样的问题。(GLib是GNOME项目的核心库,用c编写)我被告知整个插槽和信号框架都依赖于它。

Throughout the code, there are numerous instances of casting from type (1) to (2):

在整个代码中,从type(1)到(2)的转换有很多实例:

  1. typedef int (*CompareFunc) (const void *a, const void *b)
  2. typedef int (*CompareFunc) (const void *a, const void *b)
  3. typedef int (*CompareDataFunc) (const void *b, const void *b, void *user_data)
  4. typedef int (*CompareDataFunc) (const void *b, const void *b, void *user_data)

It is common to chain-thru with calls like this:

这样的电话是很常见的:

int stuff_equal (GStuff      *a,
                 GStuff      *b,
                 CompareFunc  compare_func)
{
    return stuff_equal_with_data(a, b, (CompareDataFunc) compare_func, NULL);
}

int stuff_equal_with_data (GStuff          *a,
                           GStuff          *b,
                           CompareDataFunc  compare_func,
                           void            *user_data)
{
    int result;
    /* do some work here */
    result = compare_func (data1, data2, user_data);
    return result;
}

See for yourself here in g_array_sort(): http://git.gnome.org/browse/glib/tree/glib/garray.c

您可以在g_array_sort()中看到自己:http://git.gnome.org/browse/glib/tree/glib/garray.c。

The answers above are detailed and likely correct -- if you sit on the standards committee. Adam and Johannes deserve credit for their well-researched responses. However, out in the wild, you will find this code works just fine. Controversial? Yes. Consider this: GLib compiles/works/tests on a large number of platforms (Linux/Solaris/Windows/OS X) with a wide variety of compilers/linkers/kernel loaders (GCC/CLang/MSVC). Standards be damned, I guess.

如果你是标准委员会的成员,上面的答案是详细的,而且很可能是正确的。亚当和约翰尼斯的回答经过充分研究,值得称赞。然而,在野外,您会发现这段代码工作得很好。有争议的吗?是的。考虑一下这一点:GLib在大量平台(Linux/Solaris/Windows/OS X)上编译/工作/测试,使用各种编译器/链接器/内核加载器(GCC/CLang/MSVC)。我想标准是该死的。

I spent some time thinking about these answers. Here is my conclusion:

我花了一些时间思考这些答案。这是我的结论:

  1. If you are writing a callback library, this might be OK. Caveat emptor -- use at your own risk.
  2. 如果您正在编写回调库,这可能是可以的。买者自负——风险自负。
  3. Else, don't do it.
  4. 别的,不要这样做。

Thinking deeper after writing this response, I would not be surprised if the code for C compilers uses this same trick. And since (most/all?) modern C compilers are bootstrapped, this would imply the trick is safe.

在编写完这个响应之后,如果C编译器的代码使用同样的技巧,我不会感到惊讶。由于(大多数/所有)现代C编译器都是自启动的,这意味着这个技巧是安全的。

A more important question to research: Can someone find a platform/compiler/linker/loader where this trick does not work? Major brownie points for that one. I bet there are some embedded processors/systems that don't like it. However, for desktop computing (and probably mobile/tablet), this trick probably still works.

一个更重要的问题是:有人能找到一个平台/编译器/链接器/加载器吗?布朗尼少校为那一个点了分。我敢打赌有些嵌入式处理器/系统不喜欢它。然而,对于桌面计算(可能是移动/平板电脑),这个技巧可能仍然有效。

#3


7  

The point really isn't whether you can. The trivial solution is

关键不是你能否做到。简单的解决方案是

void my_callback_function(struct my_struct* arg);
void my_callback_helper(void* pv)
{
    my_callback_function((struct my_struct*)pv);
}
do_stuff(&my_callback_helper);

A good compiler will only generate code for my_callback_helper if it's really needed, in which case you'd be glad it did.

一个好的编译器只会为my_callback_helper生成代码,如果真的需要的话,在这种情况下,你会很高兴的。

#4


4  

You have a compatible function type if the return type and parameter types are compatible - basically (it's more complicated in reality :)). Compatibility is the same as "same type" just more lax to allow to have different types but still have some form of saying "these types are almost the same". In C89, for example, two structs were compatible if they were otherwise identical but just their name was different. C99 seem to have changed that. Quoting from the c rationale document (highly recommended reading, btw!):

如果返回类型和参数类型是兼容的,那么您就有了兼容的函数类型——基本上(实际上更复杂)。兼容性和“相同类型”是一样的,只是允许有不同的类型,但仍然有某种形式说“这些类型几乎是相同的”。例如,在C89中,如果两个结构体在其他方面是相同的,但是它们的名称不同,那么它们是兼容的。C99似乎改变了这一点。引用c基本原理文档(强烈推荐阅读,顺便说一句!):

Structure, union, or enumeration type declarations in two different translation units do not formally declare the same type, even if the text of these declarations come from the same include file, since the translation units are themselves disjoint. The Standard thus specifies additional compatibility rules for such types, so that if two such declarations are sufficiently similar they are compatible.

两个不同翻译单元中的结构、联合或枚举类型声明不会正式声明相同的类型,即使这些声明的文本来自相同的include文件,因为翻译单元本身是不相交的。因此,标准为此类类型指定了附加的兼容性规则,因此,如果两个这样的声明足够相似,它们是兼容的。

That said - yeah strictly this is undefined behavior, because your do_stuff function or someone else will call your function with a function pointer having void* as parameter, but your function has an incompatible parameter. But nevertheless, i expect all compilers to compile and run it without moaning. But you can do cleaner by having another function taking a void* (and registering that as callback function) which will just call your actual function then.

这就是说,严格来说这是一个未定义的行为,因为你的do_stuff函数或者其他人会用一个函数指针来调用你的函数,这个指针有void*作为参数,但是你的函数有一个不兼容的参数。但是,尽管如此,我还是希望所有编译器在编译和运行时都不发出呻吟。但是您可以通过另一个函数获取void*(并将其注册为callback函数)来实现clean,这个函数将调用您的实际函数。

#5


2  

As C code compiles to instruction which do not care at all about pointer types, it's quite fine to use the code you mention. You'd run into problems when you'd run do_stuff with your callback function and pointer to something else then my_struct structure as argument.

由于C代码编译为指令,根本不关心指针类型,因此使用您提到的代码是很好的。当你用回调函数和指向其他东西的指针运行do_stuff时,你会遇到问题,然后my_struct结构作为参数。

I hope I can make it clearer by showing what would not work:

我希望我能通过展示什么是无效的来说明:

int my_number = 14;
do_stuff((void (*)(void*)) &my_callback_function, &my_number);
// my_callback_function will try to access int as struct my_struct
// and go nuts

or...

还是……

void another_callback_function(struct my_struct* arg, int arg2) { something }
do_stuff((void (*)(void*)) &another_callback_function, NULL);
// another_callback_function will look for non-existing second argument
// on the stack and go nuts

Basically, you can cast pointers to whatever you like, as long as the data continue to make sense at run-time.

基本上,只要数据在运行时继续有意义,您就可以对您喜欢的任何东西投下指针。

#6


0  

If you think about the way function calls work in C/C++, they push certain items on the stack, jump to the new code location, execute, then pop the stack on return. If your function pointers describe functions with the same return type and the same number/size of arguments, you should be okay.

如果您考虑函数调用在C/ c++中的工作方式,它们会在堆栈上推入某些项,跳到新的代码位置,执行,然后返回时弹出堆栈。如果函数指针描述的函数具有相同的返回类型和相同数量/大小的参数,那么应该没问题。

Thus, I think you should be able to do so safely.

因此,我认为你应该能够安全地这样做。

#7


0  

Void pointers are compatible with other types of pointer. It's the backbone of how malloc and the mem functions (memcpy, memcmp) work. Typically, in C (Rather than C++) NULL is a macro defined as ((void *)0).

空指针与其他类型的指针兼容。它是malloc和mem函数(memcpy, memcmp)工作的主干。通常,在C(而不是c++)中,NULL是定义为((void *)0的宏。

Look at 6.3.2.3 (Item 1) in C99:

查看C99中的6.3.2.3(项目1):

A pointer to void may be converted to or from a pointer to any incomplete or object type

指向void的指针可以转换为或从指向任何不完整或对象类型的指针