Подтвердить что ты не робот

Is & errno legal C?

Per 7.5,

[errno] расширяется до модифицируемого lvalue175), который имеет тип int, значение которого устанавливается на число положительных ошибок несколькими библиотечными функциями. Не указано, является ли errno макросом или идентификатором, объявленным с внешней связью. Если определение макроса подавлено для доступа к фактическому объекту или программа определяет идентификатор с именем errno, поведение undefined.

175) Макрос errno не обязательно должен быть идентификатором объекта. Он может расширяться до изменяемой lvalue, вызванной вызовом функции (например, * errno()).

Мне не ясно, достаточно ли этого, чтобы &errno не было нарушением ограничения. Язык C имеет lvalues ​​(например, переменные типа register-storage-class, однако они могут быть только автоматическими, поэтому errno не может быть определено как таковой), для которых оператор & является нарушением ограничения.

Если &errno является законным C, требуется ли постоянное?

4b9b3361

Ответ 1

Таким образом, в разделе 6.5.3.2p1 указано

Операнд унарного и оператора должен быть либо обозначением функции, результатом оператора [], либо унарного *, либо значением l, которое обозначает объект, который не является битовым полем и не объявлен в хранилище регистра -класс.

Какой я думаю можно считать означающим, что &lvalue отлично подходит для любого lvalue, который не находится в этих двух категориях. И, как вы уже упоминали, errno не может быть объявлен с помощью спецификатора класса хранения регистров, и я думаю (хотя я и не преследую ссылки на проверку прямо сейчас), что вы не можете иметь битовое поле, имеющее тип plain int.

Поэтому я считаю, что спецификация требует, чтобы &(errno) был законным C.

Если & errno является законным C, требуется ли быть постоянным?

Как я понимаю, часть точки, позволяющая errno быть макросом (и причиной, по которой она находится, например, glibc), должна позволить ей быть ссылкой на локальное хранилище потоков, и в этом случае она будет конечно, не будет постоянным по потокам. И я не вижу причин полагать, что он должен быть постоянным. Пока значение errno сохраняет указанную семантику, я не вижу причин, чтобы извращенная библиотека C не могла изменить &errno, чтобы ссылаться на разные адреса памяти в ходе программы - например, освобождая и перераспределяя резервное хранилище каждый раз, когда вы устанавливаете errno.

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

Ответ 2

Я удивлен, что никто еще не цитировал C11 spec. Извинения за длинную цитату, но я считаю, что это актуально.

7.5 Ошибки

Заголовок определяет несколько макросов...

... и

errno

который расширяется до модифицируемого lvalue (201), который имеет тип int и локальный поток время хранения, значение которого устанавливается на число положительной ошибки по несколько функций библиотеки. Если определение макроса подавляется для доступа к реальному объекту или программа определяет идентификатор с именем errno, поведение undefined.

Значение errno в исходном потоке равно нулю при запуск программы (начальное значение errno в других потоках - это неопределенное значение), но никогда не устанавливается нулем ни одной библиотекой function. (202) Значение errno может быть установлено на ненулевое значение библиотекой вызов функции независимо от того, есть или нет ошибка, при условии использования errno не описывается в описании функции в этом Международный стандарт.

(201) Макрос errno не обязательно должен быть идентификатором объекта. Он может изменяемое значение lvalue, вызванное вызовом функции (например, *errno()).

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

"Поток локальный" означает register отсутствует. Тип int означает, что битполы отсутствуют (IMO). Так что &errno выглядит законным для меня.

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

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

[обновление]

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

R. указывает, что GCC позволяет что-то подобное на верхнем уровне:

register int errno asm ("r37");  // line R

Это объявит errno как глобальное значение, хранящееся в регистре r37. Очевидно, это было бы локально модифицируемое значение lvalue. Итак, может ли соответствующая реализация C объявить errno следующим образом?

Ответ нет. Когда вы или я используем слово "декларация", мы обычно имеем в виду разговорную и интуитивную концепцию. Но стандарт не говорит разговорно или интуитивно; он говорит точно, и он направлен только на использование терминов, которые четко определены. В случае "объявления" сам стандарт определяет термин; и когда он использует этот термин, он использует свое собственное определение.

Читая спецификацию, вы можете точно узнать, что такое "объявление" и что именно. Иными словами, стандарт описывает язык "C". Он не описывает "некоторый язык, который не является C". Что касается стандарта, "C с расширениями" - это просто "некоторый язык, который не является C".

Таким образом, с стандартной точки зрения строка R вообще не является декларацией. Он даже не разбирает! Он также может читать:

long long long __Foo_e!r!r!n!o()blurfl??/**

Что касается спецификации, это так же "объявление", как строка R; т.е. не на всех.

Итак, когда спецификация C11 говорит, в разделе 6.5.3.2:

Операнд унарного оператора & должен быть либо функцией обозначение, результат оператора [] или унарного *, или lvalue, что обозначает объект, который не является бит-полем и не объявлен с спецификация хранилища регистров.

... это означает что-то очень точное, что не относится ни к чему, как Line R.

Теперь рассмотрим объявление объекта int, к которому относится errno. (Примечание: я не имею в виду объявление имени errno, так как, конечно, не может быть такого объявления, если errno есть, скажем, макрос. Я имею в виду объявление базового объекта int.)

Вышеупомянутый язык говорит, что вы можете взять адрес lvalue, если он не обозначает бит-поле или не обозначает объект "объявлен" register. И спецификация для базового объекта errno говорит, что это модифицируемое значение int l с локальной продолжительностью потока.

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

Таким образом, базовый объект errno "объявлен" в стандартном смысле, и в этом случае он не может быть как register, так и thread-local; или он вообще не объявлен, и в этом случае он не объявлен register. В любом случае, поскольку это значение l, вы можете принять его адрес.

(Если это не бит-поле, но я думаю, мы согласны с тем, что поле бит не является объектом типа int.)

Ответ 3

Первоначальная реализация errno представляла собой глобальную переменную int, согласно которой различные компоненты Библиотеки Стандартного С, используемые для указания значения ошибки, если они столкнулись с ошибкой. Однако даже в те дни нужно было быть осторожным в отношении реентерабельного кода или с вызовами функций библиотеки, которые могли бы установить errno на другое значение, поскольку вы были обрабатывая ошибку. Обычно во временную переменную сохранялось бы значение, если код ошибки был необходим в течение какого-то времени из-за возможности какой-либо другой функции или фрагмента кода, устанавливающего значение errno либо явно, либо через вызов функции библиотеки.

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

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

#define errno (*myErrno())

typedef struct {
    // various memory areas for thread local stuff
    int  myErrNo;
    // more memory areas for thread local stuff
} ThreadLocalData;

ThreadLocalData *getMyThreadData () {
    ThreadLocalData *pThreadData = 0;   // placeholder for the real thing
    // locate the thread local data for the current thread through some means
    // then return a pointer to this thread local data for the C run time
    return pThreadData;
}

int *myErrno () {
    return &(getMyThreadData()->myErrNo);
}

Тогда errno будет использоваться, как если бы это была единственная глобальная, а не потоковая безопасная переменная int с помощью errno = 0; или проверка ее как if (errno == 22) { // handle the error и даже что-то вроде int *pErrno = &errno;. Это все работает, потому что в конце локальная область данных потока распределяется и остается помещенной и не перемещается, и определение макроса, которое делает errno похожим на extern int, скрывает сантехнику его фактической реализации.

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

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

Наличие адреса errno как константы в потоке также обеспечивает обратную совместимость со старым исходным кодом, в котором используется файл errno.h include, как они должны были сделать (см. этот man-страница из linux для errno, которая явно предупреждает, чтобы не использовать extern int errno;, как было распространено в старые времена).

То, как я читаю стандарт, - это разрешить такое локальное хранилище потоков, сохраняя семантику и синтаксис, подобные старым extern int errno;, когда используется errno и позволяет использовать старое использование для какого-либо креста компилятор для встроенного устройства, которое не поддерживает многопоточность. Однако синтаксис может быть схожим из-за использования определения макросов, поэтому объявление о сокращении старого стиля не должно использоваться, потому что это объявление не соответствует действительности errno.

Ответ 4

Мы можем найти контрпример: поскольку бит-поле может иметь тип int, errno может быть битовым полем. В этом случае &errno будет недействительным. Поведение стандарта здесь явно не означает, что вы можете написать &errno, поэтому здесь используется определение поведения undefined.

C11 (n1570), § 4. Соответствие
undefined поведение указано в этом Интернационале Стандарт по словам "undefined поведение или по причине отсутствия явное определение поведения.

Ответ 5

Это похоже на допустимую реализацию, где &errno будет нарушением ограничения:

struct __errno_struct {
    signed int __val:12;
} *__errno_location(void);

#define errno (__errno_location()->__val)

Итак, я думаю, что ответ, вероятно, не...