Почему std::strlen() работает с массивами символов БЕЗ завершающих нулевых символов? Это оптимизация компилятора?
Все, что я прочитал, говорит о том, что передача непустого массива 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 ответа
Конструкция массивов означает, что любые неиспользуемые слоты удобно установлены на нулевой размер.
Так что то, что вы написали, является абсолютно законным и последовательным.
Если бы вы точно определили размер буфера для "Hello, world!"
char test_cases[4][13]
Вы бы получили "сломанный" ответ и коснулись края UB.
Кроме того, потому что вы объявили, что в качестве первого буфера он будет работать во втором буфере, поэтому даст неправильный ответ, а не какую-то фатальную ошибку.
На самом деле, если посмотреть еще раз, поскольку вы определили вторую строку как пустую, вы все равно не увидите ошибку, так как первый байт переполненных данных, возможно, также заполнен нулями!
Я говорю, возможно, потому что {} без значения на самом деле НЕ является допустимым C. Это допустимый C++11, но я не совсем уверен, должно ли поведение гарантировать, что все члены обнуляются, если стиль C++ 11 агрегатный "инициализаторы вызываются. Фактически, из-за вашего вывода {}, должно быть, сделал "правильную" вещь.
Обычно в памяти так много нулей, что ваши строки обычно заканчиваются в конце концов! Как упомянул @Джон, для иностранцев это возможность украсть деньги с вашего банковского счета.
Ваш случай - это обычный случай "нулевой инициализации". Это отлично определено.
Инициализация из заключенных в скобки списков
Когда массив инициализируется с помощью заключенного в скобки списка инициализаторов, первый инициализатор в списке инициализирует элемент массива с нулевым индексом (если не указан указатель) (начиная с 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
говорит, что передача не заканчивающегося нулем массива char в std::strlen - неопределенное поведение
Правильный.
Тем не менее, приведенный ниже код работает просто отлично.
Все строки имеют нулевое окончание и поэтому не имеют неопределенного поведения.
В любом случае, вы не можете предполагать, что программа с неопределенным поведением не будет работать "отлично". В этом нет ничего необычного.
и, вероятно, приведет к сбою программы.
Не стоит ожидать, что неопределенное поведение может "вызвать сбой программы". UB вполне может не вызвать сбой программы.