Что в действительности делает C ++, когда я случайно использую переменные для объявления длины массива? - PullRequest
3 голосов
/ 03 декабря 2009

Я помогал другу с домашним заданием на C ++. Я предупредил моего друга, что мой вид программирования (PHP, Perl, Python) сильно отличается от C ++, и не было никаких гарантий, что я не скажу ужасную ложь.

Я смог ответить на его вопросы, но не без спотыкания о моем собственном динамическом фоне. В то время как я переосмысливал себя с семантикой массива C ++, я сделал что-то глупое, как это (упрощенный пример, чтобы прояснить мой вопрос)

 #include <iostream>
 #include <cstring>
 using namespace std;
 int main()
 {
   char easy_as_one_two_three[] = {'A','B','C'};  
   int an_int = 1;

   //I want an array that has a length of the value 
   //that's currently in an_int (1)
   //This clearly (to a c++ programmer) doesn't do that.
   //but what is it doing?
   char breaking_things[an_int];

   cout << easy_as_one_two_three << endl;
   return 1;
 }

Когда я компилирую и запускаю эту программу, она выдает следующий вывод

 ABC????

Однако, если я закомментирую мое фиктивное объявление массива

 #include <iostream>
 #include <cstring>
 using namespace std;
 int main()
 {
   char easy_as_one_two_three[] = {'A','B','C'};  
   int an_int = 1;

   //I want an array that has a length of the value 
   //that's currently in an_int (1)
   //This clearly (to a c programmer) doesn't do that.
   //but what is it doing?
   //char breaking_things[an_int];

   cout << easy_as_one_two_three << endl;
   return 1;
 }

Я получаю ожидаемый результат:

 ABC

Итак, что именно здесь происходит? Я понимаю (смутно), что когда вы создаете массив, вы указываете на определенный адрес памяти, а когда вы указываете длину массива, вы говорите компьютеру «зарезервировать для меня следующие X блоков».

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

Компилятор g ++, строка версии

 science:c++ alanstorm$ g++ -v
 Using built-in specs.
 Target: i686-apple-darwin9
 Configured with: /var/tmp/gcc/gcc-5493~1/src/configure --disable-checking -enable-werror --prefix=/usr --mandir=/share/man --enable-languages=c,objc,c++,obj-c++ --program-transform-name=/^[cg][^.-]*$/s/$/-4.0/ --with-gxx-include-dir=/include/c++/4.0.0 --with-slibdir=/usr/lib --build=i686-apple-darwin9 --with-arch=apple --with-tune=generic --host=i686-apple-darwin9 --target=i686-apple-darwin9
 Thread model: posix
 gcc version 4.0.1 (Apple Inc. build 5493)

Ответы [ 7 ]

12 голосов
/ 03 декабря 2009

Обновление: Нейл указал в своем комментарии на вопрос, что вы получите ошибку, если скомпилируете это с флагами -Wall и -pedantic в g ++.

error: ISO C++ forbids variable-size array

Вы получаете ABC????, потому что он печатает содержимое массива (ABC) и продолжает печатать, пока не встретит \0.

Если бы массив был {'A','B','C', '\0'};, результат был бы ABC, как и ожидалось.

Массивы переменной длины были введены в C99 - хотя, похоже, это не относится к C ++.


Это неопределенное поведение. Даже если вы закомментируете фиктивную декларацию, печатный вывод не всегда соответствует ожидаемому (ABC). Попробуйте задать значения ASCII для некоторого печатного символа (что-то между 32 и 126) вместо an_int вместо 1, и вы увидите разницу.

an_int            output
------------------------
 40                ABC(
 65                ABCA
 66                ABCB
 67                ABCC
 296               ABC(
 552               ABC(
 1064              ABC(
 1024*1024 + 40    ABC(

Видите образец здесь? Очевидно, он интерпретирует последний байт (LSB) an_int как символ, печатает его, впоследствии каким-то образом находит нулевой символ и прекращает печать. Я думаю, что «как-то» связано с тем, что MSB-часть an_int заполнена нулями, но я не уверен (и не смог получить никаких результатов, подтверждающих этот аргумент).

ОБНОВЛЕНИЕ: речь идет о заполнении MSB нулями. Я получил следующие результаты.

ABC( для 40 - (3 нулевых байта и 40),
ABC(( для 10280 (то есть (40 << 8) + 40) - (2 нулевых байта и два 40 с), <br> ABC((( для 2631720 (то есть (10280 << 8) + 40) - (1 нулевой байт и три 40-х), <br> ABC((((°¿® для 673720360 (то есть (2631720 << 8) + 40) - нет нулевых байтов и, следовательно, печатает случайные символы до тех пор, пока не будет найден нулевой байт. <br> ABCDCBA0á´¿á´¿® для (((((65 << 8) + 66) << 8) + 67) << 8) + 68; </p>

Эти результаты были получены на процессоре с прямым порядком байтов с 8-битным размером атомного элемента и 1-байтовым приращением адреса, где 32-битное целое число 40 (0x28 в шестнадцатеричном) представлено как 0x28-0x00-0x00-0x00 (LSB) на самый низкий адрес). Результаты могут отличаться от компилятора к компилятору и от платформы к платформе.

Теперь, если вы попытаетесь раскомментировать фиктивную декларацию, вы обнаружите, что все выходные данные имеют вид ABC-randomchars-char_corresponding_to_an_int. Это снова результат неопределенного поведения.

6 голосов
/ 03 декабря 2009

Это не "заново познакомит" вас "с семантикой массива c ++", поскольку в C ++ это просто незаконно В C ++ массивы могут быть объявлены только с размерами, определенными выражениями Integral Constant (ICE). В вашем примере размер не ЛЕД. Компилируется только из-за расширения GCC.

С точки зрения C, это на самом деле совершенно законно в C99-версии языка. И он производит так называемый массив переменной длины длины 1. Таким образом, ваш «явно» комментарий неверен.

3 голосов
/ 03 декабря 2009

Вы получаете результат, который ожидаете или не ожидаете, благодаря глупой удаче. Поскольку вы не завершили NULL символы в вашем массиве, когда вы собираетесь распечатать его для cout, он напечатает A, B и C, и все остальное, что он найдет, пока не достигнет символа NULL. С объявлением массива, вероятно, что-то, что компилятор помещает в стек, чтобы размер массива во время выполнения оставлял вас с мусорными символами после A, B и C, тогда как когда вы этого не делаете, просто оказывается 0 после C в стеке.

Опять же, это просто тупая удача. Чтобы всегда получить то, что вы ожидаете, вы должны сделать: char easy_as_one_two_three[] = { 'A','B','C','\0'}; или, возможно, более полезно char easy_as_one_two_three[] = "ABC";, что приведет к нулевому завершению строки.

3 голосов
/ 03 декабря 2009

Это недопустимый синтаксис. Синтаксически просто отлично.

Это семантически неверный C ++, и он был отклонен моим компилятором (VC ++). G ++, кажется, имеет расширение, позволяющее использовать C99 VLA в C ++.

Причиной появления вопросительных знаков является то, что ваш массив из трех символов не завершен нулем; он печатает, пока не найдет ноль в стеке. На расположение стека влияют переменные, объявленные в стеке. С массивом макет таков, что перед первым нулем есть мусор; без массива нет. Это все.

2 голосов
/ 03 декабря 2009

char Break_things [an_int] выделяет массив символов размером an_int (в вашем случае 1), он называется массив переменной длины и это относительно новая функция.

В этом случае более распространено динамическое выделение памяти с использованием new:

char* breaking_things = new char[an_int]; // C++ way, C programmer would use malloc
0 голосов
/ 03 декабря 2009

Это, вероятно, не ломает вещи, которые ломают вещи. Первый массив не является строкой с завершением NUL (\ 0), что объясняет вывод - cout будет печатать все, что идет после ABC, до тех пор, пока не встретится первый NUL.

Что касается размера break_things, я подозреваю, что он отличается в разных компиляторах. Я полагаю, что по крайней мере более ранние версии gcc использовали любое значение, которое переменная имела во время компиляции, что может быть сложно определить.

0 голосов
/ 03 декабря 2009

Вывод похож на это, поскольку он будет печатать содержимое массива char до тех пор, пока не найдет нулевой символ.

Убедитесь, что массив char должен иметь строку с нулевым символом в конце и указать размер массива -> всего символов + 1 (для символа null).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...