WASM-函数指针问题

Function Pointer Issues 函数指针问题

There are two main issues with function pointers:

函数指针存在两个主要问题:

  1. Function pointer casts can cause function pointer calls to fail. 函数指针强制转换可能导致函数指针调用失败。

Function pointers must be called with the correct type: it is undefined behavior in C and C++ to cast a function pointer to another type and call it that way. This does work in most native platforms, however, despite it being UB, but in asm.js and in wasm it can fail. In that case, you may see an abort(10) or some other number, and if assertions are on you may see a message with details that start with

必须以正确的类型调用函数指针:在C和C++中将函数指针强制转换为另一种类型并以此方式调用是未定义的行为。尽管确实适用于大多数原生平台,然而尽管这种行为是UB?的,但在asm.js和wasm中,它可能会失败。在这种情况下,您可能会看到一个abort(10)或其他数字,并且如果断言出现,您可能会看到一条消息,其详细信息以下面打头

Invalid function pointer called

Rarely, you may see a compiler warning like this:

很少会看到这样的编译器警告:

warning: implicit declaration of function

This may be related to a function pointer cast problem as implicit declarations may have a different type than how you call them. However, in general the compiler cannot warn about this, and you will only see a problem at runtime.

这可能与函数指针转换问题有关,因为隐式声明的类型可能与调用它们的方式不同。但是,通常编译器无法对此发出警告,并且您只会在运行时看到问题。

  1. Older versions of clang can generate different code for C and C++ calls when a structure is passed by value (for completeness, one convention is struct byval and the other is field a, field b). The two formats are incompatible with each other, and you may get a warning.

当按值传递结构时,较旧版本的clang可以为C和C++调用生成不同的代码(出于完整性考虑,一种约定是struct byval,另一种约定是fielad a, field b)。两种格式互不兼容,您可能会收到警告。

The workaround is to pass the structure by reference, or simply not mix C and C++ in that location (for example, rename the .c file to .cpp).

解决方法是通过引用传递结构,或者直接不在该位置混合使用C和C++(例如,将.c文件重命名为.cpp)。

Debugging function pointer issues 调试函数指针问题

The SAFE_HEAP and ASSERTION options can catch some of these errors at runtime and provide useful information. You can also see if EMULATE_FUNCTION_POINTER_CASTS fixes things for you, but see later down about the overhead.

SAFE_HEAPASSERTION选项可以在运行时捕获一些这些错误,并提供有用的信息。您还可以查看是否EMULATE_FUNCTION_POINTER_CASTS为您解决了问题,但稍后可以查看有关的开销。

Working around function pointer issues 解决函数指针问题

There are three solutions to this problem (the second is preferred):

有三种解决方案(首选第二种):

  • Cast the function pointer back to the correct type before it is called. This is problematic because it requires that the caller knows the original type.在调用函数指针之前,将其强制转换回正确的类型。这是有问题的,因为它要求呼叫者知道原始类型。

  • Manually write an adapter function that does not need to be cast, and calls the original function. For example, it might ignore a parameter, and in that way bridge between the different function pointer types. 手动编写不需要强制转换的适配器函数,然后调用原始函数。例如,它可能会忽略参数,并以此方式在不同的函数指针类型之间建立桥梁。

  • Use EMULATE_FUNCTION_POINTER_CASTS. When you build with -s EMULATE_FUNCTION_POINTER_CASTS=1, Emscripten emits code to emulate function pointer casts at runtime, adding extra arguments/dropping them/changing their type/adding or dropping a return type/etc. This can add significant runtime overhead, so it is not recommended, but is be worth trying. 使用EMULATE_FUNCTION_POINTER_CASTS。当您使用构建时,Emscripten会在运行时发出代码以模拟函数指针的转换,添加额外的参数/删除它们/更改其类型/添加或删除返回类型/等。这会增加大量的运行时开销,因此不建议这样做,但值得尝试,-s EMULATE_FUNCTION_POINTER_CASTS=1

For a real-world example, consider the code below:

对于真实示例,请考虑以下代码:

#include <stdio.h>

typedef void(*voidReturnType)(const char *);

void voidReturn(const char *message) {
  printf( "voidReturn: %s
", message );
}


int intReturn(const char *message) {
  printf( "intReturn: %s
", message );
  return 1;
}

void voidReturnNoParam() {
  printf( "voidReturnNoParam:
" );
}

void callFunctions(const voidReturnType * funcs, size_t size) {
  size_t current = 0;
  while (current < size) {
    funcs[current]("hello world");
    current++;
  }
}

int main() {
  voidReturnType functionList[3];

  functionList[0] = voidReturn;
  functionList[1] = (voidReturnType)intReturn;         // Breaks in Emscripten.
  functionList[2] = (voidReturnType)voidReturnNoParam; // Breaks in Emscripten.

  callFunctions(functionList, 3);
}

The code defines three functions with different signatures: voidReturn of type vi (void (int)), intReturn of type ii, and voidReturnNoParam of type v. These function pointers are cast to type vi and added to a list. The functions are then called using the function pointers in the list.

该代码定义了三个具有不同标志的函数:voidReturn(vi),intReturn(ii)和voidReturnNoParam(v) 。这些函数指针被强制转换为类型vi并添加到列表中。然后使用列表中的函数指针调用这些函数。

The code runs (and works) when compiled to native machine code (on all major platforms). You can try it by saving the code as main.c and executing cc main.c and then ./a.out. You’ll see this output:

该代码在编译为原生机器码(在所有主要平台上)时可以运行。您可以通过将代码另存为main.c并执行cc main.c然后执行./a.out来进行尝试。您将看到以下输出:

voidReturn: hello world
intReturn: hello world
voidReturnNoParam:

However, the code fails with a runtime exception in Emscripten, and displays the console output:

但是,代码在Emscripten中由于运行时异常而失败,并显示控制台输出:

voidReturn: hello world
Invalid function pointer called with signature 'vi'. Perhaps this is an invalid value (e.g. caused by calling a virtual method on a NULL pointer)? Or calling a function with an incorrect type, which will fail? (it is worth building your source files with -Werror (warnings are errors), as warnings can indicate undefined behavior which can cause this)
Build with ASSERTIONS=2 for more info.

Note:You can try this yourself. Save the code as main.c, compile using emcc -O0 main.c -o main.html, and then load main.html into a browser.您可以自己尝试。将代码另存为main.c,使用进行编译,然后将main.html加载到浏览器中。emcc -O0 main.c -o main.html

The code fragment below shows how we can cast the function pointer back to its original signature just before calling it, so that it is found in the correct table. This requires the receiver of the table to have special knowledge about what is in the list (you can see this in the special case for index 1 in the while loop). Additionally, emcc will continue to complain about the original cast taking place in main() when adding the function to functionList[1].

下面的代码片段显示了如何在调用函数指针之前将其转换回其原始标志,以便在正确的表中找到它。这要求表的接收者对列表中的内容有特殊的了解(您可以在while循环的索引1的特殊情况中看到这一点)。此外,emcc将继续警告在向functionList[1]添加函数时main()中发生的原始强制转换。

void callFunctions(const voidReturnType * funcs, size_t size) {
  size_t current = 0;
  while (current < size) {
    if ( current == 1 ) {
      ((intReturnType)funcs[current])("hello world"); // Special-case cast
    } else {
      funcs[current]("hello world");
    }
    current++;
  }
}

The code fragment below shows how to make and use an adapter function that calls the original function. The adapter is defined with the same signature as it will have when called, and is hence available in the expected function-pointer table.

下面的代码片段显示了如何制作和使用调用原始函数的适配器函数。定义的适配器具有与调用时相同的标志,因此可以在预期的功能指针表中使用。

void voidReturnNoParamAdapter(const char *message) {
  voidReturnNoParam();
}

int main() {
  voidReturnType functionList[3];

  functionList[0] = voidReturn;
  functionList[1] = (voidReturnType)intReturn; // Fixed in callFunctions
  functionList[2] = voidReturnNoParamAdapter; // Fixed by Adapter

  callFunctions(functionList, 3);
}
原文地址:https://www.cnblogs.com/hencins/p/14119552.html