Является ли вызов strlen в snprintf причиной этого ошибки? - PullRequest
3 голосов
/ 01 июля 2010

У меня есть void *, назовите его data, длина которого я знаю, но не завершена нулем. Я звоню вот так snprintf(line, sizeof(line), "%*s", n, (const char*)data), где n - известная длина. Почти всегда это работает, но иногда это приводит к segfault.

Всякий раз, когда происходит segfault, обратный след говорит, что проблема внутри strlen. И когда я печатаю data внутри GDB, я вижу что-то вроде

(gdb) p n
$1 = 88
(gdb) p (const char*) data
$2 = 0x1d752fa8
"JASDF" ... "ADS"<Address 0x1d753000 out of bounds>
(gdb) p 0x1d753000-0x1d752fa8
$3 = 88

data действительно 88 символов, но не завершен нулем, на самом деле, кажется, что он лежит прямо против сегмента. Я предполагаю, что snprintf всегда вызывается как strlen для данных, и мне обычно везет в том, что, хотя data не завершен нулем, до того, как я попаду в сегмент, есть \0, а иногда мне не везет, и это так. Это правильно? Если да, то как обходится?

Вот как выглядит трассировка стека

#0  0x0000003c8927839e in strlen () from /lib64/libc.so.6
#1  0x0000003c89246749 in vfprintf () from /lib64/libc.so.6
#2  0x0000003c8926941a in vsnprintf () from /lib64/libc.so.6
#3  0x0000003c8924d0a3 in snprintf () from /lib64/libc.so.6

РЕДАКТИРОВАТЬ Чтобы ответить на мой собственный вопрос об обходном пути, strncpy - более подходящая функция для вызова. Я использовал snprintf по привычке.

Ответы [ 4 ]

7 голосов
/ 01 июля 2010

snprintf(line, sizeof(line), "%*s", n, (const char*)data)

data не заканчивается нулем? Тогда вы делаете это неправильно.

snprintf(line, sizeof(line), "%.*s", n, (const char*)data);

Обратите внимание на точку.

В случае строк первый * в *.*, если для требуемой длины вывода (на экране) - длина ввода, является вторым *. man printf для более.

И, очевидно, в случае %*s форматирование может вызвать strlen (), поскольку ему нужно знать, нужно ли дополнять вывод и как его дополнять.

6 голосов
/ 01 июля 2010

Похоже, ты прав. Нет гарантии, что printf не будет вызывать strlen, даже если это не обязательно в данном контексте. Вы лжете, предоставляя что-то, что не является строкой C, в качестве параметра для спецификатора форматирования %s, поэтому вы нарушили контракт printf. Неопределенные результаты поведения.

1 голос
/ 01 июля 2010

Я думаю, что ваш анализ правильный.Если буфер не завершен нулем, то вызов strlen будет считывать, пока не найдет \0.Если он проходит после конца сегмента (и следующий сегмент недопустим), то он выдаст исключение.

Решение состоит в том, чтобы завершить его нулем или поместить в другой буфер, который можно завершить нулем.

0 голосов
/ 01 июля 2010

«Обходной путь» должен использовать memcpy():

size_t validlen = n < sizeof line ? n : (sizeof line - 1);
memcpy(line, data, validlen);
line[validlen] = '\0';
...