Почему std :: strlen () работает с массивами символов БЕЗ завершающих нулевых символов? Это оптимизация компилятора? - PullRequest
0 голосов
/ 17 января 2019

Все прочитанные мною чтения говорят о том, что передача недопустимого массива char в std::strlen является неопределенным поведением и, вероятно, приведет к сбою программы. Однако приведенный ниже код (скомпилированный с g ++ на Cygwin) работает просто отлично.

Что здесь происходит?

char test_cases[4][80] = {{'H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd', '!'}, {}, {'1'}, {'A', 'B', 'C'}};
size_t num_test_cases = std::size(test_cases); // C++17

for (size_t i = 0; i < num_test_cases; ++i) 
{
    std::cout << std::strlen(test_cases[i]) << std::endl;
}

Выход:

13
0
1
3

Ответы [ 3 ]

0 голосов
/ 17 января 2019

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

То, что вы написали, полностью законно и непротиворечиво.

Если бы вы точно определили размер буфера для "Hello, world!"

char test_cases[4][13]

Вы бы получили "сломанный" ответ и коснулись бы края UB.

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

На самом деле, глядя снова, поскольку вы определили вторую строку как пустую, вы ВСЕ ЕЩЕ не увидите ошибки, поскольку первый байт переполняющих данных, возможно, также заполнен нулями!

Возможно, я говорю, потому что {} без значения на самом деле НЕ является допустимым C. Это допустимый C ++ 11, но я не совсем уверен, должно ли поведение гарантировать, что все члены обнуляются, если C ++ 11 агрегированные инициализаторы «style». Фактически, из-за вашего вывода {}, должно быть, сделал "правильную" вещь.

Обычно в памяти так много нулей, что ваши строки обычно в конечном итоге завершаются! Как упомянул @Джон, у иностранцев есть возможность украсть деньги с вашего банковского счета.

0 голосов
/ 17 января 2019

Ваш случай - это обычный случай "нулевой инициализации". Это отлично определено.

Инициализация из заключенных в скобки списков

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

Поскольку у вас выделено более 13 символов (80), все остальные заполняются '\0' (символ со значением 0). Так что strlen работает точно так, как ожидалось, потому что у вас больше места, чем вы ожидаете.

Дополнительные примеры из cppreference, которые ТОЛЬКО для вас:

int x[] = {1,2,3}; // x has type int[3] and holds 1,2,3
int y[5] = {1,2,3}; // y has type int[5] and holds 1,2,3,0,0
int z[3] = {0}; // z has type int[3] and holds all zeroes
0 голосов
/ 17 января 2019

говорит, что передача массива char с нулевым символом в конце в std :: strlen - неопределенное поведение

Правильно.

Однако приведенный ниже код работаетпросто отлично.

Все строки завершены нулем и, следовательно, не имеют неопределенного поведения.

Несмотря на это, вы не можете предполагать, что программа с неопределенным поведением не будетПохоже, "работать просто отлично".В этом нет ничего необычного.

и, скорее всего, это приведет к аварийному завершению программы.

Не стоит ожидать, что неопределенное поведение может "вызвать программу".врезаться ".UB вполне может не вызвать сбой программы.

...