Как правильно присвоить новое строковое значение?

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

#include <stdio.h>

int main(int argc, char *argv[])
{
    typedef struct
    {
        char name[20];
        char surname[20];
        int unsigned age;
    } person;

    //Here i can pass strings as values...how does it works?
    person p = {"John", "Doe",30};

    printf("Name: %s; Age: %d\n",p.name,p.age);
    // This works as expected...
    p.age = 25;
    //...but the same approach doesn't work with a string
    p.name = "Jane";

    printf("Name: %s; Age: %d\n",p.name,p.age);

    return 1;
}

Ошибка компилятора:

main.c: В функции «main»: main.c: 18: error: несовместимые типы при присвоении типу «char [20]» из типа «char *»

Я понимаю, что C (не C ++) не имеет типа String и вместо этого использует массивы символов, поэтому другим способом сделать это было изменить структуру примера для хранения указателей символов:

#include <stdio.h>

int main(int argc, char *argv[])
{
    typedef struct
    {
        char *name;
        char *surname;
        int unsigned age;
    } person;

    person p = {"John", "Doe",30};

    printf("Name: %s; Age: %d\n",p.name,p.age);

    p.age = 25;

    p.name = "Jane";

    printf("Name: %s; Age: %d\n",p.name,p.age);

    return 1;
}

Это работает, как ожидалось, но мне интересно, есть ли лучший способ сделать это. Спасибо.


person Gianluca Bargelli    schedule 28.06.2010    source источник


Ответы (3)


Первый пример не работает, потому что вы не можете присваивать значения массивам - массивы работают (вроде) как константные указатели в этом отношении. Что вы можете сделать, так это скопировать новое значение в массив:

strcpy(p.name, "Jane");

Массивы символов можно использовать, если вы заранее знаете максимальный размер строки, например в первом примере вы на 100% уверены, что имя уместится в 19 символов (а не в 20, потому что для хранения конечного нулевого значения всегда нужен один символ).

И наоборот, указатели лучше, если вы не знаете возможный максимальный размер вашей строки и / или хотите оптимизировать использование памяти, например избегайте резервирования 512 символов для имени «Джон». Однако с указателями вам необходимо динамически выделять буфер, на который они указывают, и освобождать его, когда он больше не нужен, чтобы избежать утечек памяти.

Обновление: пример динамически выделяемых буферов (с использованием определения структуры во втором примере):

char* firstName = "Johnnie";
char* surname = "B. Goode";
person p;

p.name = malloc(strlen(firstName) + 1);
p.surname = malloc(strlen(surname) + 1);

p.age = 25;
strcpy(p.name, firstName);
strcpy(p.surname, surname);

printf("Name: %s; Age: %d\n",p.name,p.age);

free(p.surname);
free(p.name);
person Péter Török    schedule 28.06.2010

Считайте строки абстрактными объектами, а массивы символов - контейнерами. Строка может быть любого размера, но контейнер должен быть как минимум на 1 больше длины строки (чтобы содержать нулевой терминатор).

В C очень мало синтаксической поддержки строк. Здесь нет строковых операторов (только операторы char-array и char-pointer). Вы не можете назначать строки.

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

Здесь можно использовать функцию strncpy(). Для максимальной безопасности я предлагаю следовать этой схеме:

strncpy(p.name, "Jane", 19);
p.name[19] = '\0'; //add null terminator just in case

Также обратите внимание на функции strncat() и memcpy().

person Artelius    schedule 28.06.2010
comment
На данный момент лучший ответ, но также хороший ответ Петера (показывает, как это сделать с помощью указателей), поэтому я жду еще немного, чтобы увидеть, могут ли другие люди добавить больше советов / предложений по этому вопросу. - person Gianluca Bargelli; 28.06.2010
comment
На несколько лет позже. Если мы сделаем что-то вроде char *s; s = "foobar"; , которое, в отличие от массивов, действительно работает. То есть я не могу char s[9]; s = "foobar". Разве мы не пытаемся присвоить строковое значение s в обоих случаях? - person mayankkaizen; 07.05.2020

Две структуры разные. Когда вы инициализируете первую структуру, выделяется около 40 байт памяти. Когда вы инициализируете вторую структуру, выделяется около 10 байт памяти. (Фактическая сумма зависит от архитектуры)

Вы можете использовать строковые литералы (строковые константы) для инициализации символьных массивов. Вот почему

person p = {"Джон", "Лань", 30};

работает в первом примере.

Вы не можете присвоить (в общепринятом смысле) строку в C.

Имеющиеся у вас строковые литералы («Джон») загружаются в память при выполнении вашего кода. Когда вы инициализируете массив одним из этих литералов, строка копируется в новую ячейку памяти. Во втором примере вы просто копируете указатель на (расположение) строкового литерала. Сделать что-то вроде:

char* string = "Hello";
*string = 'C'

может вызвать ошибки компиляции или выполнения (я не уверен.) Это плохая идея, потому что вы изменяете буквальную строку «Hello», которая, например, на микроконтроллере, может находиться в постоянной памяти.

person Gus    schedule 28.06.2010
comment
Вы правы, присваивание, которое вы написали, вызывает segfault, потому что вы пытаетесь изменить значение указателя, но в моем примере все выглядит так: char * string = Hello; строка = C; (обратите внимание, что в последнем операторе нет указателя), который работает, как ожидалось. - person Gianluca Bargelli; 28.06.2010
comment
Я хотел бы упомянуть, что изменение значения указателя не обязательно вызывает segfault. Причина, по которой фрагмент в моем исходном сообщении вызывает segfault, заключается в том, что вы пытаетесь изменить память, которая находится в ограниченном адресном пространстве. - person Gus; 28.06.2010