Меня смущает следующее сообщение об ошибке. Я не знаю, откуда здесь взялся floating-point
.
error: pointer value used where a floating-point was expected
279 | ret_value = (void *)((IntFuncWithThreeArg)closure->bound_func)((int)args_values[0], (double)args_values[1], (int)args_values[2]);
Вышеприведенное выражение представляет собой вызов замыкания ffi, которое возвращает int
следующего типа:
typedef int (*IntFuncWithThreeArg)(int, double, int);
Переменная ret_value
определяется как void *
. И args_values
, очевидно, является массивом void *
, который содержит разные типы.
Что-то не так или проблема скорее всего где-то в другом месте кода?
Обновлено:
Я постараюсь свести к минимуму важные части кода (опуская детали реализации несвязанных вещей), но компиляция минимального примера реализации полиморфного объекта не будет чем-то близким к «минимуму».
Замыкания вызываются и сохраняются внутри объектов:
typedef void (*GeneralFunc)();
typedef struct {
char *name;
char *ret_t;
char **args_t;
uint8_t args_n;
void *bound_func;
} ClosureBinding;
ClosureBinding *bind_closure(char *ret_t, char *name, GeneralFunc closure_p, char *args_t[], const uint8_t args_n, void *stream) {
ffi_cif cif;
ffi_type *args[args_n];
ffi_closure *closure;
void *bound_func;
_Bool failed;
failed = 1;
closure = NULL;
closure = ffi_closure_alloc(sizeof(ffi_closure), &bound_func);
if (closure) {
for (uint8_t i = 0; i < args_n; ++i)
args[i] = strcmp(args_t[i], "int") == 0 ? &ffi_type_sint : strcmp(args_t[i], "double") == 0 ? &ffi_type_double : &ffi_type_schar;
if (ffi_prep_cif (&cif, ABI, args_n, strcmp(ret_t, "int") == 0 ? &ffi_type_sint : strcmp(ret_t, "double") == 0 ? &ffi_type_double : &ffi_type_schar, args) == FFI_OK)
if (ffi_prep_closure_loc(closure, &cif, closure_p, stream, bound_func) == FFI_OK)
failed = 0;
}
return new_closure_binding(name, ret_t, args_t, args_n, bound_func);
}
[...]
typedef struct Object_t {
ClosureBinding **closures;
uint8_t closures_n;
void *(*call)(struct Object_t *self, int index, ...);
} Object;
void *call(Object *self, int index, ...);
Object *new_object(const uint8_t closures_n) {
Object *self = (Object *)malloc(sizeof(Object));
self->call = call;
self->closures = malloc(sizeof(ClosureBinding) *closures_n);
self->closures_n = closures_n;
return self;
}
Вероятно, наиболее важными являются детали реализации функции call
.
typedef int (*IntFuncWithThreeArg)(int, double, int);
void *call(Object *self, int index, ...) {
va_list args;
va_start(args, index);
ClosureBinding *closure = self->closures[index];
printf("Call the '%s' closure\n", closure->name);
// Get the closure argument values from variadic arguments
void *args_values[closure->args_n];
for (int i = 0; i < closure->args_n; ++i) {
char *arg;
printf("Before argument allocation\n");
arg = closure->args_t[i];
if (!strcmp(arg, "int")) {
int *p = malloc(sizeof(int));
*p = va_arg(args, int);
args_values[i] = p;
} else if (!strcmp(arg, "double") || !strcmp(arg, "float")) {
double *p = malloc(sizeof(double));
*p = va_arg(args, double);
args_values[i] = p;
}
}
void *ret_value;
if (!strcmp(closure->ret_t, "int")) {
if (closure->args_n == 3) {
ret_value = (void *)((IntFuncWithThreeArg)closure->bound_func)(*(int *)args_values[0], *(double *)args_values[1], *(int *)args_values[2]);
} else {
ret_value = (void *)-1;
fprintf(stderr, "Warning: Attempt to execute an INT type closure but argument's type is unregistered\n");
}
} else {
ret_value = (void *)-1;
fprintf(stderr, "Warning: Return type unregistered\n");
}
va_end(args);
return ret_value;
}
Вы говорите, что целевая переменная — void *ret_value
, но функция возвращает int
, а не указатель.
Кроме того: я бы обычно называл приведение int к void * красным флагом (ret_value). Ваш вопрос: если «args_values — это... массив void *», а индекс 1 на самом деле является указателем на double, то ваш второй аргумент должен быть: *(double *)args_values[1]. Я могу ошибаться, но у меня сложилось впечатление, что вы допускаете множество ошибок из-за того, что забываете разницу между указателем и тем, на что он указывает.
У ваших первого и третьего аргументов одна и та же проблема, компилятор просто не предупредил вас об этом. Таким образом, мне кажется, что обработка возвращаемого значения и всех трех аргументов fcn страдает от одной и той же проблемы.
C не определяет преобразования между void *
и плавающими типами, поэтому (double)args_values[1]
имеет неопределенное поведение. Думаю, это то, на что жалуется диагност.
Кроме того, хотя C допускает преобразования из void *
в целочисленные типы и обратно, это не значит, что это хорошая или полезная вещь для вас, которую можно делать в своем коде. void *
не является типом «Любой».
@AviBerger Спасибо, я дважды ошибся с кастингом, это правда. Хотя я знаю, что приведение void *
к int
сомнительно, но как вы думаете, что мне следует делать, когда у меня есть функция, которая вызывает общие замыкания из массива объектов? Библиотека Linux C делает то же самое для реализации буфера, например. для функции read
.
@RetiredNinja Но, господа, этот вопрос не столько о коде, сколько о том, как правильно привести эти значения.
@JohnBollinger Да, C не определяет преобразование из void *
в float
. Но это происходит с void *
по double
.
Нет, @siery, спецификация языка C не определяет преобразования между void *
и любым плавающим типом, float
, double
или long double
. Ваша реализация может предоставлять такие преобразования в качестве расширения, но спецификация языка не определяет их как разрешенные преобразования, в отличие от преобразований между void *
и целочисленными типами или между void *
и другими типами указателей на объекты.
@JohnBollinger Хм… возможно, это проблема, с которой я сталкиваюсь при переносе этого фрагмента кода на другую платформу. Можете подсказать источник этой информации. В C можно присвоить int *
void *
без приведения, верно? Вот почему компилятор не жалуется на int?
@siery, вы можете конвертировать любые типы указателей объектов. Сюда входят void *
, int *
, double *
, float *
, struct any_struct *
и другие. Вы не можете конвертировать типы указателей и плавающие типы, такие как double
. Тип double *
— это тип указателя, а не плавающий тип. Возможно, вы не осознаете, что типы указателей отличаются от типов, на которые они указывают.
И args_values, очевидно, представляет собой массив void *, который содержит разные типы.
Вы применяете void *
к double
, как показано ниже:
int main( void ) {
void *vp;
double d = (double)vp;
}
<source>:3:4: error: pointer value used where a floating-point was expected
3 | double d = (double)vp;
| ^~~~~~
Это неопределенное поведение, поэтому предупреждение.
Действительно ли args_values[1]
является указателем на двойной номер? Если это так, вам следует использовать
*(double *)args_values[1]
Если нет, вам следует использовать тип union
, а не void *
.
Аналогичные изменения следует внести и для значений int
.
Да, args_values[1]
— это указатель на двойной номер. Я должен был упомянуть об этом. Спасибо за вывод, я забыл про разыменование. Что ж, я многому учусь, работая над этим проектом.
Что ж, я знаю, что в идеале мне следует использовать объединение для типов аргументов, но сейчас я попытался изменить его на: ret_value = (void *)((IntFuncWithThreeArg)closure->bound_func)(*(int *)args_values[0], *(double *)args_values[1], *(int *)args_values[2])
. Теперь это дает мне ошибку сегментации во время выполнения.
Ошибка сегментации @siery имела бы смысл, если бы подозрение Джона Боллинджера относительно типа «Любой» было верным. Я предполагал, что ваши указатели на самом деле являются указателями. Является ли ваш args_values[1] указателем на то, где в памяти находится настоящий двойник, или фрагмент памяти, который, как вы говорите компилятору, — это одно, а вы пытаетесь вставить что-то другое?
@Ави Бергер, ОП подтвердил, что «args_values[1]
является указателем на двойник». Но да, им следует перепроверить, действительно ли это указатель. (например, double d = 1.2; args_values[1] = &d;
, а не args_values[1] = d;
.) В любом случае информации для дальнейшего обсуждения недостаточно.
@ikegami, я знаю. Так и должно быть, но комментарий ОП к ошибке сегментации заставляет меня усомниться в этом. И MS делала подобные вещи с помощью Windows API.
@Ави Бергер, я не говорил, что это должен быть указатель на двойной номер; Я сказал, что ОП уже сказал, что это так.
@AviBerger Я обновил код, отвечающий за вызов замыканий. КСТАТИ. Я думаю, что окончание средней школы — хорошее время, чтобы повзрослеть и перестать чувствовать себя главным героем. Я согласен, что мне следует меньше сосредотачиваться на гриферах и больше на ценных комиссионных, моя вина, но вы почти согласны с тем, что множество комментаторов в основном распространяют ненависть и неверную информацию. Но что ж, я думаю, сегодня это Интернет.
@siery вообще-то, я виноват. Судя по этому коду, это похоже на икегами, и вы правы, и теперь у вас есть правильные аргументы, и они не являются причиной ошибки сегмента, как я думал, они могут быть. Не знаю, что такое.
Пожалуйста, покажите код вместо его описания.