Неверные значения при инициализации 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.

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

РЕДАКТИРОВАТЬ: я тестировал константные переменные для строк и столбцов, а затем инициализировал их всеми нулями. Я чувствую, что это должно вызвать ошибку компиляции вместо того, чтобы демонстрировать это неправильное (и потенциально опасное) поведение.


person dev_nut    schedule 31.08.2018    source источник
comment
Это int opt[rows][cols] недопустимо для C++ - размеры массивов должны быть константами времени компиляции, а не переменными.   -  person    schedule 31.08.2018
comment
Массивы переменной длины не поддерживаются в C++. Если вы измените rows и columns на const, проблема будет устранена.   -  person imreal    schedule 31.08.2018
comment
@dev_nut Пожалуйста, не читайте тег C. Вопрос не имеет ничего общего с C...   -  person HolyBlackCat    schedule 31.08.2018
comment
С или С++? Выберите один тег   -  person Mad Physicist    schedule 31.08.2018
comment
Хотя массивы переменной длины являются расширением C++, в документах gccs для VLA не упоминаются проблемы с инициализацией.   -  person Shafik Yaghmour    schedule 31.08.2018
comment
Я откатился, потому что кто-то изменил переменные на const в исходном коде, что делает весь вопрос бессмысленным. Не собирался откатывать теги.   -  person dev_nut    schedule 31.08.2018
comment
Насколько я знаю, в C99 VLA не может быть инициализирован таким образом. Однако не уверен, как расширение gcc ведет себя на С++.   -  person Bob__    schedule 31.08.2018
comment
Интересно, что при тестировании на wandbox семейство gcc 4.9.x выдает все нули. Все, что выше, а ниже говорит, что массив не может быть инициализирован. Вероятно, это ошибка   -  person NathanOliver    schedule 31.08.2018
comment
Также кажется, что 1D VLA правильно инициализированы. Предлагаю отправить отчет об ошибке.   -  person HolyBlackCat    schedule 31.08.2018
comment
Даже в стандартном C вы не можете инициализировать массив переменного размера — я удивлен, что код вообще компилируется (но это должно быть расширение GCC).   -  person Jonathan Leffler    schedule 31.08.2018
comment
Скомпилированный как код C99 (замена cout на printf), GCC выдает ошибку error: variable-sized object may not be initialized.   -  person    schedule 31.08.2018
comment
@JonathanLeffler Согласен. Я поражен, что gcc вообще пошел на такое. просто компилируя код C11 с clang, VLA поддерживаются, но указанная инициализация также не. gcc действительно сделал все возможное, чтобы поддержать свое нестандартное расширение в C++.   -  person WhozCraig    schedule 31.08.2018
comment
просто используйте malloc!   -  person cat    schedule 31.08.2018
comment
просто используйте calloc! Функция calloc обнуляет память. Функция malloc не изменяет память; вы получаете то, что получаете.   -  person Thomas Matthews    schedule 01.09.2018


Ответы (3)


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

G++ поддерживает массивы переменной длины C++1y. G++ уже давно поддерживает VLA в стиле GNU/C99, но теперь дополнительно поддерживает инициализаторы и захват лямбда-выражений по ссылке. В режиме 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 с избыточными элементами инициализатора имеет комментарий, который дает некоторые сведения об этой функции:

(В ответ Якубу Елинеку из комментария №16)

Ошибка здесь в том, что G++ принимает инициализатор VLA с большим количеством элементов, чем есть место в VLA, а затем уничтожает стек во время выполнения дополнительными элементами. Это регрессия по сравнению с GCC 4.9.3, которая реализует C++ VLA, как указано в 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++ VLA будет безопаснее использовать с патчем, упомянутым в комментарии №9. Этот патч пришлось вернуть из GCC 6.0, потому что он вызывал проблемы в Java. Java был удален, и я планирую/надеюсь повторно представить патч для GCC 8. (Я хотел сделать это для GCC 7, но не смог.)

person Shafik Yaghmour    schedule 31.08.2018

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

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

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

person zneak    schedule 31.08.2018
comment
Поскольку это C++, = {} должно работать. Но на самом деле это дает мне internal compiler error. :/ - person HolyBlackCat; 31.08.2018
comment
Обратите внимание, что внутри функции C допускает непостоянные инициализаторы в нестатических (но фиксированного размера) массивах. Кроме того, ни стандартный C, ни стандартный C++ не допускают использования нуля в качестве размерности массива; это тоже расширение GCC. - person Jonathan Leffler; 31.08.2018
comment
@HolyBlackCat, как вы понимаете, что = {} должно работать? {} должен иметь тот же тип, что и объект, который он инициализирует, а этот тип неизвестен. - person zneak; 31.08.2018
comment
@zneak Ну, это работает для 1D VLA. должен иметь тот же тип, что и объект, который он инициализирует Не уверен, что понимаю. Это список инициализации в фигурных скобках, поэтому я ожидаю, что он не будет иметь никакого типа. И тип того, что мы инициализируем, известен, это int[rows][cols] (похоже, это «вариабельно модифицированный тип», еще одно расширение GCC). - person HolyBlackCat; 31.08.2018
comment
@HolyBlackCat, это не работает на C, и это не стандартный C++, и, возможно, на данный момент он также не работает на нестандартном C++. Это правда, что я перепутал детали реализации (Clang задает тип для списков инициализаторов), но C перепутал инициализаторы должны знать тип, который они инициализируют, по крайней мере, для поддержки назначенных инициализаторов. Я не думаю, что мне нужно пояснять, почему я называю неполный тип неизвестным. - person zneak; 31.08.2018

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

#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 
person dev_nut    schedule 31.08.2018
comment
Или, что еще лучше, используйте один std::vector<int> размера rows * cols. - person HolyBlackCat; 31.08.2018
comment
Конечно, это было бы более согласованно с кешем. Мне нравится двойное индексирование только потому, что оно делает более интуитивно понятным работу с 2D-матрицами, таблицами и т. д. - person dev_nut; 31.08.2018