Должен ли я поместить спецификатор класса хранения параметров в определение функции или в объявление и определение? - PullRequest
0 голосов
/ 13 февраля 2019

Я работаю над переносом старого кода K & R на ANSI C, поэтому пишу отсутствующие объявления прототипов функций.У многих определений функций есть параметры с классом хранения регистров, но я не уверен, можно ли опустить спецификатор класса хранения регистров в прототипе функции?

С определением класса хранилища регистров и без него код компилируется правильно (я пробовал GCC, VC ++ и Watcom C).Я не смог найти никакой информации в стандарте ISO / ANSI C89 о том, что делать правильно - нормально ли, если я просто введу ключевое слово register в определении функции?

int add(register int x, register int y); 

int add(register int x, register int y)
{
  return x+y;
}

Это также правильно строит:

int add(int x, int y);

int add(register int x, register int y)
{
   return x+y;
}

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

Ответы [ 5 ]

0 голосов
/ 13 февраля 2019

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

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

Более того, C89 специально говорит:

Storage-спецификатор класса в спецификаторах объявления для объявления параметра, если он присутствует, игнорируется , если объявленный параметр не является одним из членов списка типов параметров для определения функции.

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

С и без специального объявления класса хранилища кода код компилируется правильно (я пробовал gcc, VC ++ и Watcom), я не смог найти никакой информации в стандарте ISO / ANSI C89 о том, что такоеправильный способ сделать это, или это нормально, если я просто поместил ключевое слово register в определение функции?

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

ОДНАКО,

  1. Ключевое слово register является реликтовым.Компиляторы не обязаны вообще пытаться присвоить регистры register переменных, и современные компиляторы намного лучше, чем люди, решают, как назначать переменные в регистры и как-то иначе генерировать быстрый код.Пока вы конвертируете старый код, я бы воспользовался возможностью удалить все появления ключевого слова register.

  2. C89 устарело.Последняя версия стандарта - C 2018;C 2011 широко развернут;и C99 (также, технически, устаревший) доступен почти везде.Возможно, у вас есть веская причина для нацеливания на C89, но вам следует настоятельно рекомендовать вместо этого нацелиться на C11 или C18 или не менее C99.

0 голосов
/ 13 февраля 2019

Из стандарта C17, 6.7.1 Спецификаторы класса хранения :

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

Подразумевает, что компилятор будет пытаться или не зависит от компилятора ускорить доступ к параметрам, но не подразумевает каких-либо изменений всоглашение о вызовах (в основном без изменений на стороне вызова).

Таким образом, оно должно присутствовать в определении функции, но незначительно в прототипе.

0 голосов
/ 13 февраля 2019

Стандарт C89 действительно говорит это (§ 3.5.4.3 Внешние определения):

Единственный спецификатор класса хранения, который должен встречаться в объявлении параметра, это register.

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

Поскольку вы упомянули Watcom и C89, я предполагаю, что вы ориентируетесь на x86-16.Все типичные соглашения о вызовах для x86-16 (pascal, stdcall и cdecl) требуют, чтобы параметры помещались в стек, а не в регистры, поэтому я сомневаюсь, что ключевое слово фактически изменит способ передачи параметровк функции на сайте вызова.

Предположим, у вас есть следующее определение функции:

int __stdcall add2(register int x, register int y);

Функция переходит в объектный файл как _add2@4 согласно требованиям для stdcall.@ 4 указывает, сколько байтов нужно удалить из стека при возврате функции.В этом случае используется команда ret imm16 (возврат к вызывающей процедуре и извлечение байтов imm16 из стека).

add2 будет иметь следующий ret в конце:

ret 4

Если 4 байта не были помещены в стек на месте вызова (т. Е. Потому что параметры фактически были в регистрах), ваша программа теперь имеет неправильно выровненный стек и вылетает.

0 голосов
/ 13 февраля 2019

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

(что касается квалификаторов верхнего уровня, они также отбрасываются при рассмотрении совместимости типов, т. Е. void f(int); и void f(int const); являются совместимыми прототипами, но классы хранения не являются частью типов, поэтому совместимость типов отсутствует.не проблема с ними в первую очередь)

С точки зрения программиста на C, единственным наблюдаемым результатом register в C является то, что компилятор не позволит вам взять адресобъявленный объект.

Когда я делаю:

void f(int A, int register B);

void f(int register A, int B) 
{
    /*&A;*/ //doesn't compile => A does have register storage here
    &B; //compiles => B doesn't have register storage here;
        //the register from the previous prototype wasn't considered
}

, тогда &B компилируется, но &A нет, поэтому появляются только квалификаторы в определении

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

0 голосов
/ 13 февраля 2019

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

Это означает, что вы хотите запуститькаждый вариант вашего примера через компилятор, с параметром компилятора, установленным для генерации сборки вместо исполняемого файла.Посмотрите на сборку и посмотрите, сможете ли вы определить, использует ли она регистры или нет.В gcc это опция S;например:

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