Различные способы вычисления длины строки - PullRequest
3 голосов
/ 11 апреля 2011

A комментарий к одному из моих ответов оставил меня немного озадаченным. При попытке вычислить, сколько памяти требуется для объединения двух строк в новый блок памяти, было сказано, что использование snprintf предпочтительнее, чем strlen, как показано ниже :

size_t length = snprintf(0, 0, "%s%s", str1, str2);
// preferred over:
size_t length = strlen(str1) + strlen(str2);

Могу ли я получить какое-либо объяснение этому? В чем преимущество, , если есть , и увидит ли кто-нибудь один результат, отличающийся от другого?

Ответы [ 7 ]

5 голосов
/ 11 апреля 2011

Я был тем, кто сказал это, и я пропустил +1 в мой комментарий , который был написан быстро и небрежно, поэтому позвольте мне объяснить. Моя точка зрения заключалась в том, что вы должны использовать шаблон использования одного и того же метода для вычисления длины, которая в конечном итоге будет использоваться для заполнения строки, а не два разных метода, которые потенциально могут различаться тонкими способами.

Например, если у вас было три строки, а не две, и две или более из них перекрывались, вполне возможно, что strlen(str1)+strlen(str2)+strlen(str3)+1 превышает SIZE_MAX и оборачивается после нуля, что приводит к недопоставлению и усечению вывода (если используется snprintf) или чрезвычайно опасное повреждение памяти (если используются strcpy и strcat).

snprintf вернет -1 с errno=EOVERFLOW, когда результирующая строка будет длиннее INT_MAX, поэтому вы защищены. Вам нужно проверить возвращаемое значение, прежде чем использовать его, и добавить его для нулевого терминатора.

3 голосов
/ 11 апреля 2011

Если вам нужно только определить, насколько большой будет конкатенация двух строк, я не вижу особой причины предпочитать snprintf, поскольку минимальные операции для определения общей длины двух строк - это то, что два strlen звонка делают. snprintf почти наверняка будет медленнее, потому что он должен проверять параметры и анализировать строку формата, а не просто проходить две строки, считая символы.

... но ... может быть разумно использовать snprintf, если вы находитесь в сценарии, в котором вы хотите объединить две строки и иметь статический, не слишком большой буфер для обработки обычных случаев, но вы можете использовать динамически выделенный буфер в случае больших строк, например:

/* static buffer "big enough" for most cases */
char buffer[256];
/* pointer used in the part where work on the string is actually done */
char * outputStr=buffer;
/* try to concatenate, get the length of the resulting string */
int length = snprintf(buffer, sizeof(buffer), "%s%s", str1, str2);
if(length<0)
{
    /* error, panic and death */
}
else if(length>sizeof(buffer)-1)
{
    /* buffer wasn't enough, allocate dynamically */
    outputStr=malloc(length+1);
    if(outputStr==NULL)
    {
        /* allocation error, death and panic */
    }
    if(snprintf(outputStr, length, "%s%s", str1, str2)<0)
    {
        /* error, the world is doomed */
    }
}

/* here do whatever you want with outputStr */

if(outputStr!=buffer)
    free(outputStr);
0 голосов
/ 11 апреля 2011

Так что snprintf () дает мне размер строки, который был бы. Это означает, что я могу использовать malloc () для этого парня. Очень полезно.

Я хотел (но не нашел до сих пор) эту функцию snprintf (), потому что я форматирую тонны строк для вывода позже; но я не хотел назначать статические буферы для выходов, потому что трудно предсказать, как долго будут выходы. Так что у меня получилось много 4096 длинных массивов символов :-(

Но теперь - используя эту недавно обнаруженную (для меня) функцию подсчета символов snprintf (), я могу malloc () вывести буферы И спать по ночам, оба.

Еще раз спасибо и извинения перед ОП и Маттео.

0 голосов
/ 11 апреля 2011

«Преимущество», которое я вижу здесь, состоит в том, что strlen(NULL) может вызвать ошибку сегментации, в то время как (по крайней мере, glibc) snprintf() обрабатывает NULL параметры без сбоев.

Следовательно, с помощью glibc- snprintf() вам не нужно проверять, является ли одна из строк NULL, хотя length может быть немного больше, чем нужно, потому что (по крайней мере, в моей системе) printf("%s", NULL); печатает «(ноль)» вместо ничего.


Я бы не рекомендовал использовать snprintf() вместо strlen(). Это просто не очевидно. Гораздо лучшим решением является оболочка для strlen(), которая возвращает 0, если аргумент равен NULL:

size_t my_strlen(const char *str)
{
    return str ? strlen(str) : 0;
}
0 голосов
/ 11 апреля 2011

Вам нужно добавить 1 к примеру с strlen ().Помните, что вам нужно выделить место для нулевого завершающего байта.

0 голосов
/ 11 апреля 2011

РЕДАКТИРОВАТЬ: случайные, ошибочные бессмыслицы удалены. Я сказал это?

РЕДАКТИРОВАТЬ: Маттео в своем комментарии ниже абсолютно прав, и я был абсолютно неправ.

From C99:

2 Функция snprintf эквивалентна fprintf, за исключением того, что вывод записывается в массив (заданный аргументом s), а не в поток.Если n равно нулю, ничего не записывается, а s может быть нулевым указателем.В противном случае выходные символы за пределами n-1 отбрасываются, а не записываются в массив, и нулевой символ записывается в конце символов, фактически записанных в массив.Если копирование происходит между объектами, которые перекрываются, поведение не определено.

Возвращает 3 Функция snprintf возвращает количество символов, которые были бы записаны, если бы n было достаточно большим, не считая завершающий нулевой символ илиотрицательное значение, если произошла ошибка кодирования.Таким образом, вывод с нулевым символом в конце был полностью записан тогда и только тогда, когда возвращаемое значение неотрицательно и меньше n.

Спасибо, Маттео, и я прошу прощения у ОП.

Это отличная новость, потому что она дает положительный ответ на вопрос, который я задал здесь толькотри недели назад.Я не могу объяснить, почему я не прочитал все ответы, что дало мне то, что я хотел.Отлично!

0 голосов
/ 11 апреля 2011

Одним из преимуществ будет то, что входные строки сканируются только один раз (внутри snprintf()) вместо двух раз для решения strlen / strcpy.

На самом деле,перечитывая этот вопрос и комментарий к вашему предыдущему ответу, я не вижу смысла в использовании sprintf() только для вычисления длины объединенной строки.Если вы на самом деле делаете объединение, мой вышеприведенный параграф применим.

...