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

Почему GCC обманом разрешает поведение undefined просто путем помещения его в цикл?

Следующее является бессмысленным, но компилируется с помощью g++ -Wall -Wextra -Werror -Winit-self (я тестировал GCC 4.7.2 и 4.9.0):

#include <iostream>
#include <string>

int main()
{
  for (int ii = 0; ii < 1; ++ii)
  {
    const std::string& str = str; // !!
    std::cout << str << std::endl;
  }
}

Линия, отмеченная !!, приводит к поведению undefined, но GCC не диагностируется. Однако, комментируя строку for, GCC жалуется:

error: ‘str’ is used uninitialized in this function [-Werror=uninitialized]

Я хотел бы знать: почему GCC так легко обмануть здесь? Когда код не находится в цикле, GCC знает, что это неправильно. Но поместите тот же код в простой цикл, и GCC больше не понимает. Это беспокоит меня, потому что мы очень много полагаемся на компилятор, чтобы уведомить нас, когда мы делаем глупые ошибки на С++, но это не удается для кажущегося тривиального случая.

Бонусные мелочи:

  • Если вы измените оптимизацию std::string на int и, GCC будет диагностировать ошибку даже в цикле.
  • Если вы создадите сломанный код с помощью -O3, GCC буквально вызывает функцию вставки ostream с нулевым указателем для строкового аргумента. Если вы считаете, что находитесь в безопасности от нулевых ссылок, если вы не делаете никаких небезопасных кастингов, подумайте еще раз.

Я зарегистрировал ошибку GCC для этого: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63203 - Мне все равно хотелось бы лучше понять, что пошло не так, и как это может повлиять надежность аналогичной диагностики.

4b9b3361

Ответ 1

Мне бы хотелось лучше понять, что пошло не так, и как это может повлиять на надежность подобной диагностики.

В отличие от Clang, GCC не имеет логики для обнаружения самоинициализированных ссылок, поэтому получение предупреждения здесь опирается на код для обнаружения использования неинициализированных переменных, который является довольно темпераментным и ненадежным (см. Лучше неинициализированные предупреждения для обсуждения).

С помощью int компилятор может понять, что вы пишете неинициализированный int в поток, но с std::string, по-видимому, слишком много уровней абстракции между выражением типа std::string и получением const char* он содержит, и GCC не может обнаружить проблему.

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

extern "C" int printf(const char*, ...);

struct string {
  string() : data(99) { }
  int data;
  void print() const { printf("%d\n", data); }
};

int main()
{
  for (int ii = 0; ii < 1; ++ii)
  {
    const string& str = str; // !!
    str.print();
  }
}

d.cc: In function ‘int main()’:
d.cc:6:43: warning: ‘str’ is used uninitialized in this function [-Wuninitialized]
   void print() const { printf("%d\n", data); }
                                           ^
d.cc:13:19: note: ‘str’ was declared here
     const string& str = str; // !!
                   ^

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

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

Ответ 2

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

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

Я сильно подозреваю, что корпус с блочной областью на самом деле не является undefined.