присвоение var к var - это будет работать? - PullRequest
1 голос
/ 26 декабря 2011

Я пытаюсь сделать что-то полезное для обертки, над которой я работаю. Я пишу оболочку для стандартной библиотеки строк для C, потому что она мне не нравится.

Я не хочу сейчас вводить строки C в мою оболочку. У меня есть структура с именем `str, которая содержит длину строки и буфер:

struct str
{
    char *buf;
    size_t len;
};

В моем заголовочном файле у меня есть это:

typedef struct str str;

Теперь я реализовал инкапсуляцию (я думаю).

Итак, я объявляю строки следующим образом:

str *a_string = NULL;

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

str.h:

extern str *str_str(str *, char *);

и str.c

str *str_str(str *buf, char *ns)
{
    buf->len = strlen(ns);
    buf->buf = (char *) malloc(buf->len + 1);
    strncpy(buf->buf, ns, buf->len);
    return buf;
}

и тестирование это работает хорошо:

str *s = str_new();
printf("%s\n", str_cstr(str_str(s, "hello")));

output: hello

но будет ли это работать?

str_assign(s, str_str(s, "ok"));

Это, по сути, присвоение s s, я думаю. Когда я печатаю, он ничего не печатает!

Я не получаю никаких ошибок! Вся помощь очень ценится. Я довольно новичок в языке C.

Источник str_assign():

void str_assign(str *s1, str *s2)
{
    s1->len = s2->len;
    s1->buf = (char *) malloc(s2->len + 1);
    strncpy(s1->buf, s2->buf, s2->len);
}

Ответы [ 3 ]

2 голосов
/ 26 декабря 2011

Я знаю, что это C, но полезно сравнить это с C ++. Когда вы пишете оператор присваивания в C ++, вы всегда должны учитывать случай самопредставления. То же самое относится и к эквивалентной работе в C.

Поэтому, я думаю, вам нужно:

void str_assign(str *s1, const str *s2)
{
    if (s1 != s2)
    {
        free(s1->buf);  // Added!
        s1->len = s2->len;
        if ((s1->buf = (char *) malloc(s2->len + 1)) != 0)
            memcpy(s1->buf, s2->buf, s2->len + 1);
        else
            s1->len = 0;  // Is this sufficiently safe with a null buf?
    }
}

Вы можете использовать memcpy(), потому что (а) строки гарантированно не пересекаются, и (б) вы знаете, какова длина строки, поэтому вам не нужно проверять конец строки на каждом шаге так же, как и с strncpy() или strcpy().

На самом деле, есть основания утверждать, что вам никогда не нужно использовать strcpy() или strncpy() или strcat() или strncat(); вы всегда должны знать, какой длины исходная и целевая строки (иначе вы не можете быть уверены, что не будет переполнения буфера), поэтому вы всегда можете использовать memmove() или memcpy().


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

Вы также можете оптимизировать операции, выделяя новое пространство только тогда, когда новая строка длиннее старой. Вы также можете использовать realloc() вместо malloc(). Однако вы должны быть осторожны с ловушкой утечки памяти. Это неправильный способ использования realloc():

s1->buf = realloc(s1->buf, s1->len + 1);

Если realloc() терпит неудачу, вы просто перезаписываете свою точку нулем, теряя единственную ссылку на старое пространство. Вы всегда должны хранить результат realloc() в переменной, отличной от первого аргумента:

char *new_buf = realloc(s1->buf, s1->len + 1);
if (new_buf == 0)
    ...handle out of memory condition...

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

1 голос
/ 26 декабря 2011

В вашей функции str_str должен быть оператор возврата, поэтому добавьте return buf;.

Если немного подумать, вы увидите, что в вашей функции str_assign есть ошибкапотому что после изменения указателя s1->buf вы фактически изменяете s2->buf также потому, что они указывают на одну и ту же структуру.Вот реализация, которая должна работать лучше для вас:

str * str_assign(str * dest, const str * src)
{
    char * old_buffer = dest->buf;
    if (src->buf)
    {
       char * buf = malloc(src->len+1);
       strncpy(buf, src->buf, src->len);
       dest->buf = buf;
       dest->len = src->len;
    }
    else
    {
       dest->buf = 0;
    }

    if (old_buffer)
    {
      free(old_buffer);
    }
    return dest;
}
0 голосов
/ 26 декабря 2011

Я вижу memory leak в следующем коде!

void str_assign(str *s1, str *s2)
{
    s1->len = s2->len;
    s1->buf = (char *) malloc(s2->len + 1);
    strncpy(s1->buf, s2->buf, s2->len);
}

, если s1 и s2 указывают на одну и ту же область памяти, то вы выделяете новую память, используя malloc иприсваивая его указателю, который уже указывает на правильную ячейку памяти и содержит действительные данные struct str!.. этого можно избежать, поставив простую проверку, выполнив следующее

void str_assign(str *s1, str *s2)
{
    if (s1 == s2)
        return;

    /* 
    if s1 is pointing to any valid memory 
    then free it before making it point to 
    the memory that was allocated by malloc.
    */
    if (s1 != NULL)
        free(s1);

    if ((s1->buf = (char *) malloc(s2->len + 1)) == NULL) {
        printf("ERROR: unable to allocate memory\n");
        return;
    }

    s1->len = s2->len;
    strncpy(s1->buf, s2->buf, s2->len);
}
...