Универсальная функция из dlsym с разыменованным 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 stackoverflow.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 stackoverflow.c -ldl демонстрирует тот же результат, разыменование float кажется неудачным, поэтому я склоняюсь к (и надеюсь), что это поправимая вещь при правильном синтаксисе подсказки компилятору или параметры времени компиляции.


person Brian Tiffin    schedule 13.03.2016    source источник
comment
Я сомневаюсь, что это имеет какое-либо отношение к вашей проблеме, бит, использующий возвращаемое значение функции, тип возврата которой не void и которая не имеет оператора return, является неопределенным поведением.   -  person Daniel Jour    schedule 14.03.2016
comment
Хороший момент, обновил листинг.   -  person Brian Tiffin    schedule 14.03.2016


Ответы (1)


C99 и C11 6.5.2.2p6 состояния

Если выражение, обозначающее вызываемую функцию, имеет тип, который не включает прототип, целочисленные повышения выполняются для каждого аргумента, а аргументы с типом float повышаются до удвоения. Это называется продвижением аргументов по умолчанию.

и 6.5.2.2p7 продолжается с

Если выражение, обозначающее вызываемую функцию, имеет тип, который действительно включает прототип, аргументы неявно преобразуются, как если бы путем присвоения, в типы соответствующих параметров, принимая тип каждого параметра как неквалифицированную версию его объявленного тип. Обозначение с многоточием в деклараторе прототипа функции вызывает остановку преобразования типа аргумента после последнего объявленного параметра. По умолчанию продвижение аргументов выполняется для конечных аргументов.

Итак, когда вы вызываете функцию с параметром float, прототип («подпись», как вы ее называете) сообщает компилятору, что нужно не преобразовывать float в double. (Аналогично для целочисленных типов меньше int.)

Исправление, очевидно, заключается в использовании прототипов («подписей»). Вы должны, если хотите передать 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 (). (Идиома *(void **)(&funcptr), рекомендованная в более старых версиях, больше не рекомендуется; она была все равно глупо.)

person Nominal Animal    schedule 14.03.2016
comment
Спасибо. Будет немного сложно изменить кодогенератор, но это определенно путь к решению (добавление приведения указателя на функцию, когда типы аргументов известны). Возможно, вы только что порадовали некоторых программистов COBOL более. Протестировано в MVCE и сейчас работает. Пора разобраться с проходом патча компилятора. - person Brian Tiffin; 14.03.2016