Безопасно ли вызывать функцию C с большим количеством аргументов, чем она ожидает?

Я неоднократно сталкивался с проблемой настройки обработчиков сигналов в коде GTK+, не нуждаясь в нескольких параметрах и испытывая соблазн использовать ту же функцию, что и обработчик для нескольких сигналов, чьи обработчики имеют разные сигнатуры, но с первыми n аргументами (те, о которых я забочусь) то же самое.

безопасно ли это (в том смысле, что это не неопределенное поведение, а не более прагматичный смысл "работает ли он на моем ПК?") передавать указатели на функции GObject API, когда эти функции ожидают меньше аргументов, чем они фактически получат от процесса излучения сигнала?

или, чтобы развести это с GTK+, этот код в порядке?

/* Note: No void *userdata argument! */
void show(int x) {
  printf("x = %dn", x);
}

void do_stuff(void (*fn)(int, void *), void *userdata) {
  static int total = 0;
  (*fn)(total, userdata);
  total++;
}

void doitnow(void) {
  do_stuff(&show, NULL);
}

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

Edit: An почти идентичный вопрос зондирует "совместимый тип функции" более близко, и рисует ответ непосредственно обращаясь к моей конкретной проблеме - это связывание обработчиков сигналов GObject. TL; DR: Да, это неопределенное поведение, но оно практически идиоматично (хотя и не обязательно) в некоторых наборах инструментов.

3 ответов


это явно неопределенное поведение, согласно 6.5.2.2, параграф 9:

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

тип show определен с

void show(int x)

не совместим с типом выражения, на которое указывает указатель, через который он крикнул,

void (*fn)(int, void *)

также прямо в пункте 8 пункта 6.3.2.3:

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

для функций, "совместимый тип" охарактеризован в 6.7.6.3 (15) [6.7.5.3 (15) в С99]:

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

наиболее непосредственно относящаяся часть здесь заключается в том, что количество аргументов должно быть одинаковым.


это неопределенное поведение в стандарте C, но стандарт C также требует поддержки функций с вариативными аргументами, и единственный способ компиляторов реализовать последнее-разрешить первое.

эта конкретная идиоматическая форма поддерживается каждым компилятором и платформой, поддерживаемой GLib, и была в течение многих лет - даже до того, как GLib был создан - поэтому маловероятно, что она когда-либо будет нарушена.


читать http://www.unixwiz.net/techtips/win32-callconv-asm.html и http://www.csee.umbc.edu / ~chang / cs313.S02 / stack.shtml Кажется, если вы проходите больше, это не должно вызвать проблемы, потому что сначала параметры выталкиваются. Поэтому все, что не требуется, остается в стеке вызывающего абонента. Но противоположный путь наверняка вызовет проблемы.