Неправильные значения при инициализации 2D-массива до 0 в gcc

#include <iostream>
using namespace std;

int main() {

    int rows = 10;
    int cols = 9;
    int opt[rows][cols] = {0};

         for (int i = 0; i < rows; ++i) {
            for (int j = 0; j < cols; ++j) {
                std::cout << opt[i][j] << " ";
            }
             std::cout << "\n";
         }

    return 0;
}

Выход:

0 32767 1887606704 10943 232234400 32767 1874154647 10943 -1 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 

Я использую gcc 6.3, https://www.codechef.com/ide

Я ожидаю, что первая строка будет нулевой. Разве это не так?

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

Ответы

Ответ 1

Если мы посмотрим на примечания к выпуску gcc 4.9, похоже, что они добавили поддержку для инициализации VLA, ожидая, что VLA будет поддерживаться в будущей версии C++:

G++ поддерживает C++ 1y массивы переменной длины. G++ долгое время поддерживал GNU/C99-стиль VLA, но теперь дополнительно поддерживает инициализаторы и захват лямбда по ссылке. В режиме C++ 1y G++ будет жаловаться на использование VLA, которые не разрешены стандартом проекта, например, формирование указателя на тип VLA или применение sizeof к переменной VLA. Обратите внимание, что теперь кажется, что VLA не будут частью C++ 14, но будут частью отдельного документа, а затем, возможно, C++ 17.

Мы можем видеть, что до 4,9 жалоб мы не можем инициализировать VLA

error: variable-sized object 'opt' may not be initialized  
     int opt[rows][cols] = {0};  
                             ^

но в 4.9.1 и после того, как он перестает жаловаться, и у него нет такой же ошибки, как в более поздних версиях.

Это похоже на регресс.

Обратите внимание, что clang отказывается разрешить инициализацию VLA (который они поддерживают как расширение), см. Живой пример. Что имеет смысл, поскольку C99 не разрешает инициализацию VLA:

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

gcc Ошибка 69517

Отчет об ошибке gcc : SEGV в VLA с избыточными элементами инициализатора имеет комментарий, который содержит некоторые сведения об этой функции:

(В ответ на Jakub Jelinek из комментария № 16)

Ошибка здесь в G++, принимающая инициализатор VLA с большим количеством элементов, чем есть место для VLA, а затем сбрасывание стека во время выполнения с дополнительными элементами. Это регрессия в отношении GCC 4.9.3, которая реализует VLA C++, как указано в n3639 (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3639.html), Это задокументировано в изменениях GCC 4.9 (https://gcc.gnu.org/gcc-4.9/changes.html), что подчеркивает эту функцию, используя следующий пример:

  void f(int n) {
    int a[n] = { 1, 2, 3 }; // throws std::bad_array_length if n < 3
    ...

VLA были впоследствии удалены из C++, а также частично (но не полностью) удалены из G++, что заставляет C++ программы разрабатываться и тестироваться с G++ 4.9, чтобы разбить при портировании на более позднюю версию.

C++ VLAs будет более безопасным для использования с патчем, указанным в комментарии # 9. Патч должен был быть возвращен из GCC 6.0, поскольку он вызвал проблемы на Java. Java был удален, и я планирую/надеюсь повторно отправить патч для GCC 8. (Я хотел сделать это для GCC 7, но не добрался до него).

Ответ 2

Это, похоже, ошибка GCC, и желаемое поведение, скорее всего, не должно компилироваться. C99 поддерживает массивы переменной длины, но отказывается их инициализировать: инициализаторы C должны знать свой тип во время компиляции, но тип массива переменной длины не может быть завершен во время компиляции.

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

Обратите внимание, что even = {0} является технически опасным (если он вообще работает): если rows и cols равны 0, вы будете переполнены. Возможно, Memset - ваш лучший вариант.

Ответ 3

Я поставил этот вопрос, чтобы понять, что не так с моим кодом или gcc. Но именно так я сделал бы это в C++. Используйте векторы вместо массивов для требований массива переменной длины.

#include <iostream>
#include <vector>

int main() {

    int rows = 10;
    int cols = 9;

    std::vector<std::vector<int>> opt(rows, std::vector<int>(cols, 0));

         for (int i = 0; i < rows; ++i) {
            for (int j = 0; j < cols; ++j) {
                std::cout << opt[i][j] << " ";
            }
             std::cout << "\n";
         }

    return 0;
}

Выход:

0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0