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

Я пытаюсь понять, как решить эту тривиальную проблему в 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: ошибка: несовместимые типы, когда присвоение типа 'char [20] из типа 'char *

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

#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;
}

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

Ответ 1

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

strcpy(p.name, "Jane");

Char массивы прекрасно подходят, если вы знаете максимальный размер строки заранее, например. в первом примере вы на 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);

Ответ 2

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

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

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

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

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

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

Ответ 3

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

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

человек p = { "Джон", "Доу", 30};

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

Вы не можете назначить (в обычном смысле) строку в C.

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

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

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