Может ли тип указателя на функцию, который не принимает аргументов и возвращает void, использоваться с функциями, которые принимают аргументы и возвращают значение? - PullRequest
0 голосов
/ 14 марта 2020

В библиотеке GTK можно найти следующее определение:

/**
 * GCallback:
 * 
 * The type used for callback functions in structure definitions and function 
 * signatures. This doesn't mean that all callback functions must take no 
 * parameters and return void. The required signature of a callback function 
 * is determined by the context in which is used (e.g. the signal to which it 
 * is connected). Use G_CALLBACK() to cast the callback function to a #GCallback. 
 */
typedef void  (*GCallback)              (void);

Это, вероятно, typedef для указателей на функции, которые не принимают параметров и возвращают void.

Однако - в примере Hello World на сайте GTK представлен следующий код:

g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);

Где activate - это функция, которая возвращает void, но принимает два параметра. (G_CALLBACK - это макрос, который просто приводит к GCallback).

И действительно - комментарий над typedef GCallback предполагает, что:

Это не означает что все функции обратного вызова не должны принимать никаких параметров и возвращать void

Этот код действительно компилируется и выполняется. Так как это возможно?

1 Ответ

2 голосов
/ 14 марта 2020

Стандарт C11 §6.3 Преобразования , а точнее §6.3.2.3 Указатели ¶8 говорит:

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

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

g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);

Где activate - это функция, которая возвращает void, но принимает два параметра. (G_CALLBACK - это макрос, который просто приводит к GCallback).

Предположим, что два параметра int; их тип совпадает с обсуждением.

extern void activate(int, int);

Код в g_signal_connect() получает 4 указателя. Третий - обратный звонок; формально он имеет тип void (*callback)(void).

. Код внутри g_signal_connect() ожидает обратного вызова с двумя целыми числами (arg1 и arg2), поэтому его необходимо использовать:

((void (*)(int, int)callback)(arg1, arg2);

для принудительного перевода типа 'generi c' callback на правильный тип указателя на функцию - в противном случае нельзя избежать вызова неопределенного поведения. Вы должны знать, что для g_signal_connect() требуется такой указатель, как параметр обратного вызова, приведенный к типу generi c, и вы должны передать ему такой указатель, соответствующий приведенному типу.

Также помните, что Один из способов продемонстрировать «неопределенное поведение» - это «вести себя, как ожидается, даже если ожидания не гарантируются стандартом». Другие способы демонстрации неопределенного поведения включают сбой или повреждение памяти.


Примечание.

C11 §6.2.5 Типы ¶28 говорит:

Указатель на void должен иметь те же требования к представлению и выравниванию, что и указатель на тип символа. 48) Аналогично, указатели на квалифицированные или неквалифицированные версии совместимые типы должны иметь одинаковые требования к представлению и выравниванию. Все указатели на типы конструкций должны иметь те же требования к представлению и выравниванию, что и другие. Все указатели на типы объединения должны иметь те же требования к представлению и выравниванию, что и другие. Указатели на другие типы не обязательно должны иметь одинаковые требования к представлению или выравниванию.

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

Требования §6.3.2.3¶8, по-видимому, подразумевают, что все указатели на разные типы функций должны иметь одинаковые требования к представлению и выравниванию друг с другом; в противном случае становится трудно гарантировать требование о преобразовании туда и обратно в §6.3.2.3¶8.

Другое последствие §6.2.5¶28 состоит в том, что вы не можете надежно преобразовать указатель на тип функции и указатель на тип объекта, такой как void *. Это имеет последствия для таких функций, как dlsym(); их трудно использовать чисто - компилятор, вероятно, будет жаловаться, если у вас включены строгие уровни предупреждения.

Компиляция некоторого кода, который преобразует между указателем на функцию и указателем на объект (и наоборот), G CC 9.3 .0 с gcc -std=c99 -O3 -Wall -pedantic -Wdeclaration-after-statement -Wold-style-definition -Wold-style-declaration -Wnested-externs -Wmissing-prototypes -Werror … дает:

…: error: ISO C forbids conversion of function pointer to object pointer type [-Werror=pedantic]
…: error: ISO C forbids conversion of object pointer to function pointer type [-Werror=pedantic]

Это предупреждение, если у вас не действует -Werror или -pedantic-errors, и игнорируется, если у вас нет -pedantic или -pedantic-errors в действии.

Остерегайтесь различий между -pedantic (он же -Wpedantic) и -pedantic-errors, как задокументировано G CC в Параметры запроса или подавления предупреждений .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...