Вызов функции C со слишком небольшим количеством аргументов

Я работаю над некоторым устаревшим кодом C. Исходный код был написан в середине 90-х годов для Solaris и компилятора Си Sun той эпохи. Текущая версия компилируется под GCC 4 (хоть и с множеством предупреждений), и вроде работает, но я пытаюсь ее привести в порядок -- хочу выжать как можно больше скрытых ошибок, пока определяю, что может понадобиться для исправления. адаптируйте его к 64-битным платформам и компиляторам, отличным от того, для которого он был создан.

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

Пример:

импл.с:

int foo(int one, int two) {
  if (two) {
      return one;
  } else {
      return one + 1;
  }
}

клиент1.с:

extern foo();
int bar() {
  /* only one argument(!): */
  return foo(42);
}

клиент2.с:

extern int foo();
int (*foop)() = foo;
int baz() {
  /* calls the same function as does bar(), but with two arguments: */
  return (*foop)(17, 23);
}

Вопросы: определен ли результат вызова функции с отсутствующими аргументами? Если да, то какое значение получит функция для неопределенного аргумента? В противном случае компилятор Sun C версии ок. 1996 (для Solaris, а не VMS) продемонстрировали предсказуемое поведение, зависящее от реализации, которое я могу эмулировать, добавляя определенное значение аргумента к затронутым вызовам?


person John Bollinger    schedule 11.07.2013    source источник
comment
Я не думаю, что у вас есть доступ к Sun box с установкой Solaris 1996 года, не так ли? Было бы здорово заблокировать его и посмотреть, что происходит.   -  person paddy    schedule 12.07.2013
comment
Если отсутствующие аргументы не оказывают отрицательного влияния на результат функции, почему бы не заполнить их пробелами? 0 или NULL и т. д.   -  person Kninnug    schedule 12.07.2013
comment
Есть шанс, что вы пропустили макрос препроцессора?   -  person Robert Harvey    schedule 12.07.2013
comment
@kninnug: Это не очень жизнеспособный вариант, если не понимать, почему это происходит.   -  person Robert Harvey    schedule 12.07.2013
comment
Возможный дубликат stackoverflow.com/questions/5929711 /   -  person Jimbo    schedule 12.07.2013
comment
@paddy: нет, у меня нет оборудования или компилятора/ОС для проверки поведения в исходной целевой среде.   -  person John Bollinger    schedule 12.07.2013
comment
@kninnug: функция использует значение иногда не переданного аргумента. Я пытаюсь определить, надежно ли он работает в исходной целевой среде, используя поведение, зависящее от реализации (которое я затем могу эмулировать), или иногда он дает сбой (в этом случае мне нужно выяснить, каким было желаемое поведение).   -  person John Bollinger    schedule 12.07.2013
comment
@Robert Harvey: хороший вопрос, но, насколько я могу судить, нет, я не пропустил макрос.   -  person John Bollinger    schedule 12.07.2013
comment
@Jimbo: вопрос, на который вы ссылаетесь, касается тех же тем, но дело обстоит наоборот: параметры передаются при вызове функции, когда определение функции не объявляет никаких. Я могу только пожелать, чтобы это была моя проблема.   -  person John Bollinger    schedule 12.07.2013


Ответы (4)


РЕДАКТИРОВАТЬ: я нашел поток стека C без параметров поведения, который дает очень краткий и конкретный, точный ответ. Комментарий PMG в конце ответа касается UB. Ниже были мои первоначальные мысли, которые, я думаю, совпадают и объясняют, почему поведение является UB..

Вопросы: определен ли результат вызова функции с отсутствующими аргументами?

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

Если да, то какое значение получит функция для неопределенного аргумента?

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

Есть два основных способа передачи параметров, о которых я знаю... (В Википедии есть хорошая страница на соглашения о вызовах)

  1. Пройти регистрацию. То есть ABI (бинарный интерфейс приложения) для платформы скажет, что регистры x и y, например, предназначены для передачи параметров, а все остальные, которые передаются через стек...
  2. Все проходит через стек...

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

РЕДАКТИРОВАТЬ: Основываясь на другом потоке стека, который я нашел, я бы исправил вышеизложенное, чтобы сказать, что extern объявил функцию без параметров для объявленной функции с «неуказанным (но не переменным) числом параметров».

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

В противном случае компилятор Sun C версии ок. 1996 (для Solaris, а не VMS) продемонстрировали >> предсказуемое поведение, зависящее от реализации.

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

person Jimbo    schedule 11.07.2013
comment
Спасибо. Похоже, что SPARC ABI определяет передачу по регистру до шести параметров, так что это то, с чем изначально имел дело код. Я еще не нашел документацию по компилятору, но склонен согласиться с тем, что компилятор вряд ли изменит регистры, которые не соответствуют (как он думает) аргументам функции. - person John Bollinger; 12.07.2013
comment
Я знаю! Я просто вызову random(), чтобы получить значение для второго аргумента! :) - person John Bollinger; 12.07.2013

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

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

Выдержит ли программа такие неверные вызовы, также будет зависеть от соглашения о вызовах. «Классическое» соглашение о вызовах C, в котором вызывающая сторона отвечает за помещение параметров в стек и удаление их оттуда, будет менее подвержена сбоям при наличии таких ошибок. То же самое можно сказать и о вызовах, использующих регистры ЦП для передачи аргументов. Между тем, соглашение о вызовах, в котором функция сама отвечает за очистку стека, практически сразу рухнет.

person AnT    schedule 11.07.2013
comment
Спасибо, примерно так я и думал. Похоже, SPARC ABI требует, чтобы первые шесть аргументов передавались в регистры, но я не нашел ничего о касании регистров из этой группы, которые не соответствуют аргументам. Вероятно, тогда вызываемая функция получает случайный мусор, если вызывающая сторона не указывает все аргументы. - person John Bollinger; 12.07.2013

Очень маловероятно, что функция bar когда-либо в прошлом давала согласованные результаты. Единственное, что я могу себе представить, это то, что он всегда вызывается на свежем пространстве стека, и пространство стека очищается при запуске процесса, и в этом случае второй параметр будет равен 0. Или разница между возвратом one и one+1 не имеют большое значение в большей сфере применения.

Если это действительно так, как вы изображаете в своем примере, то перед вами большой толстый жук. В далеком прошлом существовал стиль кодирования, при котором функции vararg реализовывались путем указания большего количества параметров, чем было передано, но, как и в случае с современным varargs, вы не должны обращаться ни к каким параметрам, которые на самом деле не переданы.

person Bryan Olivier    schedule 11.07.2013
comment
Это действительно очень похоже на то, что я изображаю. Значение иногда не передаваемого параметра проверяется в управляющем выражении условного оператора в вызываемой функции (но в остальном оно не используется). Я согласен с тем, что это выглядит как ошибка, но я хотел (1) подтвердить это и (2) получить некоторые рекомендации о том, что может быть правильным решением. - person John Bollinger; 12.07.2013
comment
@JohnBollinger (1) Я думаю, мы все согласны с тем, что это ошибка. (2) Боюсь, вам придется исправить ошибку, поняв, каким должно быть значение отсутствующего параметра. - person Bryan Olivier; 12.07.2013

Я предполагаю, что этот код был скомпилирован и запущен на архитектуре Sun SPARC. Согласно этой древней веб-странице SPARC: "регистрирует %o0-%o5 используются для первых шести параметров, передаваемых в процедуру."

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

Если у вас есть доступ к исходному исполняемому файлу и вы можете разобрать код вокруг неправильного места вызова, вы сможете сделать вывод, какое значение имело %o1 при вызове. Или вы можете попробовать запустить исходный исполняемый файл на эмуляторе SPARC, таком как QEMU. В любом случае это не будет тривиальной задачей!

person markgz    schedule 11.07.2013
comment
Спасибо. Я также нашел это: sparc.com/standards/FpsABI3rd.pdf, который содержит более подробное (и более техническое) описание ABI. Это согласуется с тем, что вы сказали. - person John Bollinger; 12.07.2013