Неверное чтение strdup с размером 4, когда строковый литерал заканчивается символом новой строки \ n

Я получаю недопустимую ошибку чтения, когда строка src заканчивается на \n , ошибка исчезает, когда я удаляю \n :

 #include  #include  #include  int main (void) { char *txt = strdup ("this is a not socket terminated message\n"); printf ("%d: %s\n", strlen (txt), txt); free (txt); return 0; } 

Выход valgrind:

 ==18929== HEAP SUMMARY: ==18929== in use at exit: 0 bytes in 0 blocks ==18929== total heap usage: 2 allocs, 2 frees, 84 bytes allocated ==18929== ==18929== All heap blocks were freed -- no leaks are possible ==18929== ==18929== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0) ==18929== ==18929== 1 errors in context 1 of 1: ==18929== Invalid read of size 4 ==18929== at 0x804847E: main (in /tmp/test) ==18929== Address 0x4204050 is 40 bytes inside a block of size 41 alloc'd ==18929== at 0x402A17C: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so) ==18929== by 0x8048415: main (in /tmp/test) ==18929== ==18929== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0) 

Как исправить это, не жертвуя новым символом линии?

    Речь идет не о символе новой строки, а о спецификаторе формата printf. Вы обнаружили, что, возможно, ошибка в strlen() , и я могу сказать, что вы должны использовать gcc.

    Ваш программный код в порядке. Спецификатор формата printf может быть немного лучше, но это не приведет к ошибке valgrind, которую вы видите. Давайте посмотрим на эту ошибку valgrind:

     ==18929== Invalid read of size 4 ==18929== at 0x804847E: main (in /tmp/test) ==18929== Address 0x4204050 is 40 bytes inside a block of size 41 alloc'd ==18929== at 0x402A17C: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so) ==18929== by 0x8048415: main (in /tmp/test) 

    «Неверное чтение размера 4» – это первое сообщение, которое мы должны понять. Это означает, что процессор выполнил команду, которая загрузила бы 4 последовательных байта из памяти. Следующая строка указывает, что адрес, который был прочитан, был «Адрес 0x4204050 равен 40 байтам внутри блока размера 41 alloc’d».

    С этой информацией мы можем понять это. Во-первых, если вы замените '\n' на '$' или любой другой символ, будет произведена та же ошибка. Попытайся.

    Во-вторых, мы видим, что ваша строка содержит 40 символов. Добавление символа завершения \0 приводит к тому, что общие байты используются для представления строки до 41.

    Поскольку у нас есть сообщение «Адрес 0x4204050 составляет 40 байт внутри блока размером 41 alloc’d», мы теперь знаем все о том, что происходит не так.

    1. strdup() выделил правильный объем памяти, 41 байт.
    2. strlen() попытался прочитать 4 байта, начиная с 40-го числа, которое будет распространяться на несуществующий 43-й байт.
    3. valgrind поймал проблему

    Это ошибка glib (). Когда-то начинался проект под названием Tiny C Compiler (TCC). По совпадению, glib был полностью изменен, так что нормальные строковые функции, такие как strlen() больше не существовали. Они были заменены оптимизированными версиями, которые считывают память с использованием различных методов, таких как чтение по четыре байта за раз. gcc был изменен одновременно с тем, чтобы генерировать вызовы для соответствующих реализаций, в зависимости от выравнивания указателя ввода, скомпилированного оборудования и т. д. Проект TCC был оставлен, когда это изменение среды GNU затруднило создание новый компилятор C, убрав возможность использовать glib для стандартной библиотеки.

    Если вы сообщите об ошибке, поддерживающие glib, вероятно, не исправит ее. Причина в том, что при практическом использовании это, вероятно, никогда не приведет к фактическому сбою. Функция strlen считывает байты 4 за раз, потому что видит, что адреса выровнены по 4 байт. Всегда можно читать 4 байта из 4-байтового выровненного адреса без segfault, учитывая, что чтение 1 байта с этого адреса будет успешным. Поэтому предупреждение от valgrind не выявляет потенциального сбоя, просто несоответствие в предположениях о том, как программировать. Я считаю, что valgrind технически корректен, но я думаю, что нет никаких шансов, что разработчики glib сделают все, чтобы отменить предупреждение.

    Сообщение об ошибке, похоже, указывает на то, что это strlen который читается за буфером malloc ed, выделенным strdup . На 32-битной платформе оптимальная реализация strlen может читать по 4 байта за раз в 32-битный регистр и делать некоторые бит-twiddling, чтобы увидеть, есть ли там нулевой байт. Если в конце строки осталось меньше 4 байтов, но 4 байта по-прежнему читаются для выполнения проверки нулевого байта, тогда я мог видеть, что эта ошибка печатается. В этом случае предположительно разработчик strlen будет знать, «безопасно» ли это сделать на конкретной платформе, и в этом случае ошибка valgrind является ложным положительным.