порядок кода с массивом переменной длины

В C99 есть большая разница между этими двумя?:

int main() {
    int n , m;
    scanf("%d %d", &n, &m);
    int X[n][m];

    X[n-1][m-1] = 5;
    printf("%d", X[n-1][m-1]);
}

и:

int main(int argc, char *argv[]) {
    int n , m;
    int X[n][m];
    scanf("%d %d", &n, &m);

    X[n-1][m-1] = 5;
    printf("%d", X[n-1][m-1]);
}

Кажется, что первый работает всегда, тогда как второй работает для большинства входов, но выдает ошибку сегментации для входов 5 5 и 6 6 и возвращает значение, отличное от 5, для входа 9 9. Итак, вам нужно убедиться, что вы получили значения, прежде чем объявлять их с помощью массивов переменной длины, или здесь происходит что-то еще?


person Austin    schedule 07.07.2016    source источник
comment
Второе совершенно неверно... так как n и m не инициализированы. int X[n][m]; использует значения, которые m и n имеют в этой точке кода - вы не можете просто установить их позже, и вы не можете изменить размер массива, изменив их позже.   -  person Dmitri    schedule 07.07.2016
comment
Хорошо, спасибо, хотел убедиться, что это просто заказ, а не что-то еще, о чем я не подумал.   -  person Austin    schedule 07.07.2016


Ответы (3)


Если сработает второй, это чистая случайность. Тот факт, что это когда-либо работает, доказывает, что, к счастью, компиляторы еще не могут сделать демоны вылетают из твоего носа.

Объявление переменной не обязательно инициализирует ее. В этом случае int n, m; оставляет как n, так и m значения undefined, и попытка доступа к этим значениям является поведением undefined. Если необработанные двоичные данные в памяти, которые указывают на эти точки, случается интерпретироваться в значение, превышающее значения, введенные для n и m, что очень и очень далеко от гарантированного, тогда ваш код будет Работа; если нет, то не будет. Ваш компилятор также мог вызвать этот segfault или расплавить ваш процессор; это неопределенное поведение, так что все может случиться.

Например, предположим, что область памяти, которую компилятор выделяет для n, содержит число 10589231, а m получило 14. Если затем вы введете n из 12 и m из 6, вы станете золотым — массив окажется достаточно большим. С другой стороны, если n получил 4, а m получил 2, тогда ваш код будет смотреть за конец массива, и вы получите неопределенное поведение, которое может даже не сломаться, поскольку вполне возможно, что биты, хранящиеся в четырехбайтовые сегменты после конца массива доступны как для вашей программы, так и для допустимых целых чисел в соответствии с вашим компилятором/стандартом C. Кроме того, n и m могут иметь отрицательные значения, что приводит к... странным вещам. Наверное.

Конечно, все это вздор и предположения, зависящие от компилятора, ОС, времени суток и фазы луны,1, и вы не можете полагаться на то, что какие-либо числа будут инициализированы справа те.

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


Просто для ясности: хотя при некоторых обстоятельствах переменные должны быть инициализированы нулями, это не означает, что вы должны полагаться на такое поведение. Вы всегда должны явно указывать переменным значение по умолчанию или инициализировать их как можно скорее после их объявления (в случае использования чего-то вроде scanf). Это делает ваш код более понятным и не дает людям задаваться вопросом, полагаетесь ли вы на этот тип UB.


1: Источник: Райан Бемроуз, в чате

person Fund Monica's Lawsuit    schedule 07.07.2016

int X[n][m]; означает объявить массив, измерения которого являются значениями, которые в настоящее время имеют n и m. Код C не смотрит в будущее; операторы и объявления выполняются в том порядке, в котором они встречаются.

Во втором коде вы не указали значения n или m, так что это неопределенное поведение, что означает, что может случиться что угодно.

Вот еще один пример последовательного выполнения:

int x = 5;
printf("%d\n", x);
x = 7;

Это напечатает 5, а не 7.

person M.M    schedule 07.07.2016
comment
Этот пост кажется... более чем саркастичным и снисходительным, правда. Может быть, дело в том, что это такая простая вещь, которая кажется мне очевидной, поэтому видеть любое объяснение этому звучит снисходительно. - person Fund Monica's Lawsuit; 07.07.2016
comment
@QPaysTaxes Я просто пытаюсь говорить прямо, без сарказма или снисходительности - person M.M; 07.07.2016
comment
Справедливо. Это, наверное, все в моей голове. - person Fund Monica's Lawsuit; 07.07.2016

Второй должен создавать ошибки, потому что n и m инициализируются довольно случайными значениями, если они являются локальными переменными. Если они глобальные, они будут со значением 0.

person Andros Rex    schedule 07.07.2016
comment
любая версия С++ - VLA - это вещь C. В С++ их нет. - person user2357112 supports Monica; 07.07.2016
comment
Ах, да. Вы можете использовать C++ и по-прежнему разрешать их, поэтому я забыл об этом. - person Andros Rex; 07.07.2016