Инициализация объектов со статической длительностью хранения в C против C++

Возможный дубликат:
Что возвращает основной?

Например, следующий код компилируется без предупреждения:

#include <stdio.h>

int i = i + 1;

int main(int argc, char *argv[])
{

    fprintf (stderr, "%d\n", i);

    return 0;
}

Я думаю, что это незаконно в синтаксисе, потому что i используется до того, как объявлено, верно?

И на мой взгляд, появление int i = i + 1; безусловно, ошибка, почему компилятор не предупреждает об этом? Я использую GCC 4.5.1.

9 ответов

Решение

(обратите внимание: я имею в виду текущий стандарт C++)

Я не совсем уверен в этом, но, если моя интерпретация стандарта верна, код должен быть в порядке, а не UB.

Первой инициализацией этой переменной является нулевая инициализация объектов со статической продолжительностью хранения, которая происходит до того, как произойдет любая другая инициализация (§3.6.2 ¶1).

Итак, прежде всего i установлен на ноль.

Затем происходит динамическая инициализация (т. Е. Ненулевая и непостоянная инициализация), поэтому она использует текущее значение i (0), чтобы фактически инициализировать его снова. В конце его следует оценить до 1.

Это, кажется, подтверждается §8.5 ¶6, который прямо говорит:

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

(Если вы обнаружите какой-то недостаток в анализе, пожалуйста, просто скажите мне в комментариях, и я буду рад исправить / удалить ответ, это скользкий пол, и я осознаю это:))

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

В C это законно Кстати

int main()
{
   int i = i+1;
}

3.3.1/1 Пункт объявления

Точка объявления имени находится сразу после его полного декларатора и до его инициализатора (если есть).

Поведение хорошо определено согласно §3.6.2/1 который говорит:

"Объекты со статической продолжительностью хранения (3.7.1) должны быть инициализированы нулями (8.5) перед любой другой инициализацией".

Ваш код не является допустимым C.

Если ваш компилятор компилирует его без диагностики,
ваш компилятор не является компилятором C

Вы должны использовать константы для инициализации переменной.

В вашем коде выражение инициализатора (i + 1) не является константой.

Это нарушает 6.7.8/4:

Все выражения в инициализаторе [...] должны быть константными выражениями или строковыми литералами.

Код недопустим в C.

initializer element is not constant

C99 - 6.7.8 Инициализация

Все выражения в инициализаторе для объекта со статической продолжительностью хранения должны быть константными выражениями или строковыми литералами.

Действителен в C++.

Стандартные состояния C++ в 3.6.2. Инициализация нелокальных объектов:

Объекты со статической продолжительностью хранения (3.7.1) должны быть инициализированы нулями (8.5) перед любой другой инициализацией.

Действительно ли это синтаксически незаконно, я не уверен (это было бы определенно верно в методе). Однако, как вы предполагаете, это семантическая проблема, и компилятор должен выдать предупреждение как i был использован без инициализации. Однако IMO компилятор C/C++ обычно не предупреждает об этом (например, Java может выдать ошибку), хотя вы можете включить такое предупреждение, добавив -Wall параметр в gcc.

Вы не можете присвоить значение переменной, используя другую переменную вне какой-либо функции. Заявление i + 1; оценивается во время выполнения, в то время как int i = i + 1; находится вне какой-либо функции, поэтому его нужно оценивать во время компиляции.

Чтобы ответить на ваш вопрос о "i используется до того, как объявлено, верно?

Не в C++. [basic.scope.pdecl] говорит

Пункт объявления для имени находится сразу после его полного декларатора (пункт 8) и до его инициализатора (если таковой имеется), за исключением случаев, указанных ниже. [ Пример:

int  x  =  12;
{  int  x  =  x;  }

Здесь второй x инициализируется своим собственным (неопределенным) значением. - конец примера ]

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

  1. Создайте слот памяти для "я".
  2. Инициализируйте память до нуля (нормальное поведение по умолчанию).
  3. Прочитайте значение "i" (которое равно нулю).
  4. Добавьте 1.
  5. Храните его в "я".

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

int * p = malloc( 10 * sizeof *p );

Если использование p в правой части было запрещено, что будет ошибка компилятора. Вы можете обойти это, явно указав тип в rhs:

int * p = malloc( 10 * sizeof(int) );

Но это может привести к незначительным ошибкам, если в дальнейшем изменить тип, так как компилятор не обнаружит этот случай:

double * p = malloc( 10 * sizeof(int) ); // will compile and probably cause havoc later

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

int i = i + 1;
//      ^  uninitialized read

Однако в C++ существуют другие ситуации, когда вы можете передать ссылку / указатель на неинициализированные объекты, и это прекрасно. Рассматривать:

template <typename T>
struct NullObjectPattern { // intentionally out of order:
   T* ptr;
   T null;
   NullObjectPattern() : ptr( &null ), null() {}

   T& operator*() { return *ptr; }
   T* operator->() { return ptr; }
};

В то время как null еще не был инициализирован, использование его в выражении, которое принимает только его адрес (но не разыменовывает его), хорошо определено: ячейка памяти существует, она была выделена и присутствует. Сам объект не был инициализирован, и в результате разыменования он вызовет UB, но тот факт, что неинициализированный объект используется в выражении, не означает, что код на самом деле неверен.

Другие вопросы по тегам