来自 dlsym 的通用函数,带有取消引用的浮点数
Generic function from dlsym with dereferenced float
GnuCOBOL 编译器通过使用动态符号查找支持动态 CALL,但这里的 MCVE 严格是 C 语言,并且比证明(我认为的)4 字节和 8 字节大小都有效的要小一些。
这是 AMD-64,所以 sizeof *float 不等于 sizeof float。
问题仅在从 dlsym 查找中被通用(在本例中未签名)函数指针调用时取消引用浮点数时才会出现。
// gcc -Wl,--export-dynamic -g -o byval byval.c -ldl
#include <stdio.h>
#include <dlfcn.h>
// hack in a 1 / 3 float 0.303030, 1050355402 as 32bit int
unsigned char field[4] = {0xca, 0x26, 0x9b, 0x3e};
// and a 1 / 6 double, 0.151515
unsigned char dtype[8] = {0x64, 0x93, 0x4d, 0x36, 0xd9, 0x64, 0xc3, 0x3f};
int aroutine(float);
int
main(int argc, char** argv)
{
float* fp = (float*)field;
double g;
void* this;
int (*calling)();
int result;
/* call the routines using generic data treated as float */
float f = *fp;
printf("Initial: %f \n", f);
printf("\nBy signature\n");
result = aroutine(*(float*)(field));
this = dlopen("", RTLD_LAZY);
printf("\nGeneric: (busted, stack gets 0x40000000)\n");
calling = dlsym(this, "aroutine");
result = calling(*(float*)(field));
printf("\nBy reference: (works when callee dereferences)\n");
calling = dlsym(this, "proutine");
result = calling((float*)(field));
printf("\nGeneric double (works):\n");
calling = dlsym(this, "droutine");
result = calling(*(double*)(dtype));
printf("\nGeneric int and double (works):\n");
calling = dlsym(this, "idroutine");
result = calling(*(int*)(field),*(double*)(dtype));
printf("\nGeneric int and float (busted) and int:\n");
calling = dlsym(this, "ifiroutine");
result = calling(*(int*)(field), *(float*)(field), *(int*)(field));
return 0;
}
int aroutine(float f) {
printf("aroutine: %f\n", f);
return 0;
}
int proutine(float *fp) {
printf("proutine: %f\n", *fp);
return 0;
}
int droutine(double g) {
printf("droutine: %g\n", g);
return 0;
}
int idroutine(int i, double g) {
printf("idroutine: %d %g\n", i, g);
return 0;
}
int ifiroutine(int i, float f, int j) {
printf("ifiroutine: %d %f %d\n", i, f, j);
return 0;
}
运行 的
prompt$ gcc -Wl,--export-dynamic -g -o mcve Whosebug.c -ldl
prompt$ ./mcve
Initial: 0.303030
By signature
aroutine: 0.303030
Generic: (busted, stack gets 0x40000000)
aroutine: 2.000000
By reference: (works when callee dereferences)
proutine: 0.303030
Generic double (works):
droutine: 0.151515
Generic int and double (works):
idroutine: 1050355402 0.151515
Generic int and float (busted) and int:
ifiroutine: 1050355402 2.000000 1050355402
我想我需要了解一下 64 位 ABI 在取消引用浮点数据时如何处理未签名的调用。
包含 COBOL 标签,因为这破坏了 GnuCOBOL (生成 C 中间体) 当使用 FLOAT-SHORT (C float)使用 CALL BY VALUE,而 FLOAT-LONG (C double) CALL BY VALUE 有效,32 位整数也是如此。
顺便说一句,我很确定这不是 gcc 中的错误,因为 tcc tcc -rdynamic -g -o tccmcve Whosebug.c -ldl
显示相同的输出,浮动取消引用似乎很无聊,所以我倾向于(并希望)这是一个可以解决的问题,只要给编译器或编译时选项适当的语法提示。
C99 和 C11 6.5.2.2p6 状态
If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions.
和 6.5.2.2p7 继续
If the expression that denotes the called function has a type that does include a prototype, the arguments are implicitly converted, as if by assignment, to the types of the corresponding parameters, taking the type of each parameter to be the unqualified version of its declared type. The ellipsis notation in a function prototype declarator causes argument type conversion to stop after the last declared parameter. The default argument promotions are performed on trailing arguments.
因此,当您调用具有 float
参数的函数时,原型(您调用的 "signature")告诉编译器 而不是 将 float
转换为 double
。 (类似于小于 int
的整数类型。)
显然,解决方法是使用原型 ("signatures")。如果您想传递 float
、char
或 short
,您 必须 ,因为没有原型,它们将被提升为 double
、int
和 int
。
然而,这真的不应该是一种负担。如果你有一些无原型函数指针,比如
int (*generic)() = dlsym(self, "aroutine");
并且您想调用一个原型为 void foo(float, int, double)
的函数,您可以随时转换函数指针:
((void (*)(float, int, double))generic)(f_arg, i_arg, d_arg);
尽管使用具有正确原型的临时函数指针肯定更易于阅读和维护:
{
void (*call_foo)(float, int, double) = (void *)generic;
call_foo(f_arg, i_arg, d_arg);
}
看到POSIX dlsym() documentation for reference. (The *(void **)(&funcptr)
idiom recommended in older versions不再推荐;无论如何,这很愚蠢。)
GnuCOBOL 编译器通过使用动态符号查找支持动态 CALL,但这里的 MCVE 严格是 C 语言,并且比证明(我认为的)4 字节和 8 字节大小都有效的要小一些。
这是 AMD-64,所以 sizeof *float 不等于 sizeof float。
问题仅在从 dlsym 查找中被通用(在本例中未签名)函数指针调用时取消引用浮点数时才会出现。
// gcc -Wl,--export-dynamic -g -o byval byval.c -ldl
#include <stdio.h>
#include <dlfcn.h>
// hack in a 1 / 3 float 0.303030, 1050355402 as 32bit int
unsigned char field[4] = {0xca, 0x26, 0x9b, 0x3e};
// and a 1 / 6 double, 0.151515
unsigned char dtype[8] = {0x64, 0x93, 0x4d, 0x36, 0xd9, 0x64, 0xc3, 0x3f};
int aroutine(float);
int
main(int argc, char** argv)
{
float* fp = (float*)field;
double g;
void* this;
int (*calling)();
int result;
/* call the routines using generic data treated as float */
float f = *fp;
printf("Initial: %f \n", f);
printf("\nBy signature\n");
result = aroutine(*(float*)(field));
this = dlopen("", RTLD_LAZY);
printf("\nGeneric: (busted, stack gets 0x40000000)\n");
calling = dlsym(this, "aroutine");
result = calling(*(float*)(field));
printf("\nBy reference: (works when callee dereferences)\n");
calling = dlsym(this, "proutine");
result = calling((float*)(field));
printf("\nGeneric double (works):\n");
calling = dlsym(this, "droutine");
result = calling(*(double*)(dtype));
printf("\nGeneric int and double (works):\n");
calling = dlsym(this, "idroutine");
result = calling(*(int*)(field),*(double*)(dtype));
printf("\nGeneric int and float (busted) and int:\n");
calling = dlsym(this, "ifiroutine");
result = calling(*(int*)(field), *(float*)(field), *(int*)(field));
return 0;
}
int aroutine(float f) {
printf("aroutine: %f\n", f);
return 0;
}
int proutine(float *fp) {
printf("proutine: %f\n", *fp);
return 0;
}
int droutine(double g) {
printf("droutine: %g\n", g);
return 0;
}
int idroutine(int i, double g) {
printf("idroutine: %d %g\n", i, g);
return 0;
}
int ifiroutine(int i, float f, int j) {
printf("ifiroutine: %d %f %d\n", i, f, j);
return 0;
}
运行 的
prompt$ gcc -Wl,--export-dynamic -g -o mcve Whosebug.c -ldl
prompt$ ./mcve
Initial: 0.303030
By signature
aroutine: 0.303030
Generic: (busted, stack gets 0x40000000)
aroutine: 2.000000
By reference: (works when callee dereferences)
proutine: 0.303030
Generic double (works):
droutine: 0.151515
Generic int and double (works):
idroutine: 1050355402 0.151515
Generic int and float (busted) and int:
ifiroutine: 1050355402 2.000000 1050355402
我想我需要了解一下 64 位 ABI 在取消引用浮点数据时如何处理未签名的调用。
包含 COBOL 标签,因为这破坏了 GnuCOBOL (生成 C 中间体) 当使用 FLOAT-SHORT (C float)使用 CALL BY VALUE,而 FLOAT-LONG (C double) CALL BY VALUE 有效,32 位整数也是如此。
顺便说一句,我很确定这不是 gcc 中的错误,因为 tcc tcc -rdynamic -g -o tccmcve Whosebug.c -ldl
显示相同的输出,浮动取消引用似乎很无聊,所以我倾向于(并希望)这是一个可以解决的问题,只要给编译器或编译时选项适当的语法提示。
C99 和 C11 6.5.2.2p6 状态
If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions.
和 6.5.2.2p7 继续
If the expression that denotes the called function has a type that does include a prototype, the arguments are implicitly converted, as if by assignment, to the types of the corresponding parameters, taking the type of each parameter to be the unqualified version of its declared type. The ellipsis notation in a function prototype declarator causes argument type conversion to stop after the last declared parameter. The default argument promotions are performed on trailing arguments.
因此,当您调用具有 float
参数的函数时,原型(您调用的 "signature")告诉编译器 而不是 将 float
转换为 double
。 (类似于小于 int
的整数类型。)
显然,解决方法是使用原型 ("signatures")。如果您想传递 float
、char
或 short
,您 必须 ,因为没有原型,它们将被提升为 double
、int
和 int
。
然而,这真的不应该是一种负担。如果你有一些无原型函数指针,比如
int (*generic)() = dlsym(self, "aroutine");
并且您想调用一个原型为 void foo(float, int, double)
的函数,您可以随时转换函数指针:
((void (*)(float, int, double))generic)(f_arg, i_arg, d_arg);
尽管使用具有正确原型的临时函数指针肯定更易于阅读和维护:
{
void (*call_foo)(float, int, double) = (void *)generic;
call_foo(f_arg, i_arg, d_arg);
}
看到POSIX dlsym() documentation for reference. (The *(void **)(&funcptr)
idiom recommended in older versions不再推荐;无论如何,这很愚蠢。)