моя версия strlcpy - PullRequest
       0

моя версия strlcpy

4 голосов
/ 29 мая 2010

gcc 4.4.4 c89

Моя программа много копирует строки. Я не хочу использовать strncpy, поскольку он не завершается. И я не могу использовать strlcpy, поскольку он не переносимый.

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

Это достаточно хорошо для производства?

size_t s_strlcpy(char *dest, const char *src, const size_t len)
{
    size_t i = 0;

    /* Always copy 1 less then the destination to make room for the nul */
    for(i = 0; i < len - 1; i++)
    {
        /* only copy up to the first nul is reached */
        if(*src != '\0') {
            *dest++ = *src++;
        }
        else {
            break;
        }
    }

    /* nul terminate the string */
    *dest = '\0';

    /* Return the number of bytes copied */
    return i;
}

Большое спасибо за любые предложения,

Ответы [ 9 ]

11 голосов
/ 29 мая 2010

Хотя вы можете просто использовать другую функцию strlcpy, как рекомендует другой пост, или использовать snprintf(dest, len, "%s", src) (который всегда завершает буфер), вот что я заметил, глядя на ваш код:

size_t s_strlcpy(char *dest, const char *src, const size_t len)
{
    size_t i = 0;

Нет необходимости делать len константой здесь, но это может быть полезно, так как он проверяет, чтобы убедиться, что вы не модифицировали его.

    /* Always copy 1 less then the destination to make room for the nul */
    for(i = 0; i < len - 1; i++)
    {

К сожалению. Что если len равно 0? size_t обычно не подписан, поэтому (size_t) 0 - 1 в конечном итоге станет чем-то вроде 4294967295, в результате чего ваша программа будет перемещаться по памяти вашей программы и вылетать на неотображенную страницу.

        /* only copy up to the first nul is reached */
        if(*src != '\0') {
            *dest++ = *src++;
        }
        else {
            break;
        }
    }

    /* nul terminate the string */
    *dest = '\0';

Код выше выглядит хорошо для меня.

    /* Return the number of bytes copied */
    return i;
}

Согласно Википедии , strlcpy возвращает strlen(src) (фактическую длину строки), а не количество скопированных байтов. Следовательно, вам нужно продолжать считать символы в src, пока вы не нажмете '\0', даже если оно превышает len.

Кроме того, если ваш цикл for завершается при условии len - 1, ваша функция вернет len-1, а не len, как вы ожидаете.


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

Вот закомментированная версия того, как я написал бы strlcpy:

size_t s_strlcpy(char *dest, const char *src, size_t len)
{
    char *d = dest;
    char *e = dest + len; /* end of destination buffer */
    const char *s = src;

    /* Insert characters into the destination buffer
       until we reach the end of the source string
       or the end of the destination buffer, whichever
       comes first. */
    while (*s != '\0' && d < e)
        *d++ = *s++;

    /* Terminate the destination buffer, being wary of the fact
       that len might be zero. */
    if (d < e)        // If the destination buffer still has room.
        *d = 0;
    else if (len > 0) // We ran out of room, so zero out the last char
                      // (if the destination buffer has any items at all).
        d[-1] = 0;

    /* Advance to the end of the source string. */
    while (*s != '\0')
        s++;

    /* Return the number of characters
       between *src and *s,
       including *src but not including *s . 
       This is the length of the source string. */
    return s - src;
}
4 голосов
/ 29 мая 2010

ИМХО, просто заприте оригинал strlcpy , о котором кратко заявил Игнасио Васкес-Абрам. Код OpenBSD проверен на битву, а условия лицензии зашкаливают;).

Что касается вашего кода, то, что я бы добавил к тому, что уже было сказано другими, это просто вопрос личного вкуса:

/* only copy up to the first nul is reached */
if(*src != '\0') {
    *dest++ = *src++;
}
else {
    break;
}

Я бы написал это так:

if(*src == '\0') {
    break;
}
*dest++ = *src++;

И потому, что он уменьшает количество ненужного кода, который нужно читать людям, и потому, что это мой «стиль» - писать последовательно таким образом, вместо if (ok) { do } else { handle error }. Комментарий над if также является избыточным (см. Комментарий над циклом for).

3 голосов
/ 29 мая 2010

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

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

Без сборки и отладки:

str = memccpy (dest, src, '\0', len);
if(str)
    *str = '\0';
2 голосов
/ 29 мая 2010

Существует СУХОЙ принцип «не повторяйся». Другими словами, не создавайте новый код, чтобы сделать что-то, что уже является сделкой - проверьте в стандартной библиотеке C, как показано в примере выше (WhilrWind).

Одной из причин является упомянутое тестирование. Стандартная библиотека C тестировалась годами, поэтому можно с уверенностью сказать, что она работает так, как рекламируется.

Учиться, играя с кодом - отличная идея, продолжайте пытаться.

2 голосов
/ 29 мая 2010

Я бы предположил, что Тестирование белого ящика полезно для такой функции (форма модульного тестирования).

1 голос
/ 29 мая 2010

Модульное тестирование? Это достаточно хорошо для производства?

Вероятно, для такой "простой" функции, как эта, этого может быть достаточно, хотя единственный реальный способ проверить функцию - это попытаться ее сломать.

Передайте ему указатели NULL, строки длиной 10 тыс. Символов, отрицательные значения len, поврежденные данные и т. Д. В общем, подумайте: если бы вы были злонамеренным пользователем, пытающимся его взломать, как бы вы это сделали?

Смотрите ссылку в моем ответе здесь

1 голос
/ 29 мая 2010

Я думаю, что ошибочно полагаться на длину и делать арифметику.

Тип size_t не подписан. Подумайте, как будет вести себя ваша функция, если вызывается с адресатом 0 размера.

1 голос
/ 29 мая 2010
1 голос
/ 29 мая 2010

Да, юнит-тестирование. Проверьте с большим количеством случайно сгенерированных строк.

Выглядит хорошо для меня, хотя.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...