Зачем хранить функцию с другим типом аргумента для указателя на функцию с void * аргумент UB? - PullRequest
4 голосов
/ 06 июня 2019

Я недавно наткнулся на интересный вопрос (по крайней мере, я так думаю).Небольшой пример:

Пример

#include <stdio.h>

typedef struct A {
    int x;
} A;

int (*func)(void*, void*);

int comp(A* a, A* b) {
    return a->x - b->x;
}

int main() {
    func = comp;
    A a;
    A b;
    a.x = 9;
    b.x = 34;
    printf("%d > %d ? %s\n", a.x, b.x, func(&a, &b) > 0 ? "true" : "false");
}

Я спрашивал себя, является ли приведенный выше код верным, но при компиляции GCC выдает предупреждение: warning: assignment from incompatible pointer type.Я провел некоторое исследование, и в одном потоке кто-то заявил, что вышеизложенное будет неопределенным поведением , теперь мне интересно, почему это UB, поскольку void* может быть безопасно приведен к любому возможному другому типу.Это просто стандартная поговорка «Нет, вот и не определено», или есть какая-то объяснимая причина?Все вопросы по StackOverflow, которые я нашел, указывают его UB, но не совсем так.Может быть, это как-то связано с разыменованием внутреннего указателя на функцию?

Ответы [ 3 ]

5 голосов
/ 06 июня 2019

A void * можно безопасно преобразовать в / из любого другого типа, но это не то преобразование, которое вы пытаетесь сделать.Вы пытаетесь конвертировать int (*)(A *, A *) в int (*)(void *, void*).Это две совершенно разные вещи.

Автоматическое преобразование void * не применяется к аргументам в указателе функции.Для совместимости двух указателей на функции число и тип аргументов должны быть совместимы, а также тип возвращаемого значения.

Одна из причин этого заключается в том, что void * не обязательно должно иметь такое же представление, какдругие типы указателей.Это хорошо при простом преобразовании в void * и обратно, что явно разрешено стандартом, однако это может быть проблемой при вызове функции.

Предположим, что void * представлен 8 байтами, а указатель структуры представлен 4 байтами.В вашем примере два 8-байтовых значения будут помещены в стек, но два 4-байтовых значения будут считаны из стека в качестве параметров функции.Это приведет к недопустимым значениям указателя, которые впоследствии будут разыменованы.

2 голосов
/ 06 июня 2019

6.7.5.3 p15

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

Вопросы рекурсивно сводятся к тому, совместим ли A* с void*.

6.7.5.1 p2

Для совместимости двух типов указателей оба должны иметь одинаковую квалификацию, и оба должны быть указателями на совместимые типы.

Тип A не совместим с void.

1 голос
/ 06 июня 2019

Как указали dbush и alinsoar, проблема в том, что int (*)(void *, void *) и int (*)(A *, A *) несовместимы. Чтобы исправить это, измените определение comp следующим образом:

int comp( void *a, void *b )
{
  A *aa = a;
  A *bb = b;

  return aa->x - bb->x;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...