String vs String Literal: прямое назначение или strcpy / strncpy? - PullRequest
2 голосов
/ 17 апреля 2019

Мне кажется, у меня есть проблемы с пониманием строки и строкового литерала.

Это то, что я узнал из своего класса, при переходе к функции const char * указывает, что это строковый литерал, а char * обозначает строку.

Предположим, у меня есть объявление структуры:

struct node{
    char *name;
    struct node *next;
};

и функцию, это функция, которую я хочу реализовать:

void load_buffer(struct node *input_node, char *input_name);

Предполагается, что эта функция присваивает input_name имени члена структуры.

Отсюда мое замешательство. В теле load_buffer я должен написать:

input_node->name = input_name;

Или я должен использовать strcpy / strncpy для этого?

strcpy(input_node->name, input_name);// Suppose it's safe in this case.

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

Спасибо за помощь. :)

Ответы [ 3 ]

3 голосов
/ 17 апреля 2019

... при передаче в функцию, const char * указывает, что это строковый литерал, а char * обозначает строку.

Не совсем. const char * объявляет, что функция не будет пытаться изменить строку. Так что он идеально подходит для струнных букв, потому что они не могут быть изменены.

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

void load_buffer(struct node *input_node, const char *input_name) {
    input_node->name = name;
}

struct node nodes[2];
char buf[4];
const char *data[] = { "foo", "bar"};
for (int i=0; i<2; i++) {
    strcpy(buf, data[i]);    // suppose it is read from somewhere (file, stdin, socket)
    load_buffer(node + i, buf);
}

Оба объекта node будут иметь свой элемент name, указывающий на строку buf от вызывающей стороны, и оба будут указывать на "bar" (содержимое buf в конце цикла)!

Если вы хотите сохранить значение строки во время вызова, вам следует скопировать его в выделенную память:

void load_buffer(struct node *input_node, const char *input_name) {
    input_node->name = strdup(name);  // allocation errors should be tested...
}

Но затем вы должны освободить элемент name, когда узел больше не используется ...

3 голосов
/ 17 апреля 2019

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

input_node->name = input_name;

В случае strcpy указатели в каждом узле будут указывать на разные местоположения.Перед этим вам нужно создать память для каждого указателя.

input_node->name = malloc(strlen(input_name)+1); //Allocate memory first.
strcpy(input_node->name, input_name);// Suppose it's safe in this case.

Для визуализации: enter image description here

1 голос
/ 17 апреля 2019

Сначала обратимся к некоторой терминологии:

  • это строковый литерал: "I am a string literal"
  • это тип: char* (он же указатель на символ)
  • это также тип: const char* (он же указатель на константу char)
  • это объявление переменной типа char*: char* str
  • этообъявление переменной типа const char*: const char* cstr

Указатель не является строковым литералом.Указатель может указывать на строковый литерал, массив, только на один элемент или может иметь значение null.

В C строка представляет собой массив символов с нулевым символом в конце.

В C вы можете присвоить char* переменную строковому литералу, но изменение строкового литерала недопустимо, поэтому настоятельно рекомендуется никогда этого не делать.Причина, по которой это разрешено, историческая.

char* a = "asd"; // allowed, but frowned upon
// a points to a string literal, so we can say a is a string
// a is not a string literal

char b = 'x';
char* c = &b;
// c points to a single char
// we cannot say c is a string

char d[10] = "asd";
// d is a char array. Its content is a string, so we can say d is a string.
// d is not a string literal
// the string literal is copied into the array d

char* e = d; // or equivalent char* e = &d[0];
// e points to a string

char f[4] = {'a', 's', 'd', '\0'};
// f is an array. Its content is a string, so we can say f is a string

char* g = f;
// g points to a string. We can say g is a string

char h[3] = {'a', 's', 'd'};
// h is an array. Its content is NOT a string, because the char array is not null terminated

char* i = h;
// i is not a string

А теперь еще раз пройдемся по всему вышеописанному, но не заменяйте char на const char, и все комментарии остаются в силе (за исключением того, что `const char* a = "asd" теперь в порядке).


А теперь к проблеме.

Есть два сценария:

  1. Каждый узел имеет свою собственную строку и «владеет» этой строкой.Каждый узел отвечает за выделение памяти для строки и освобождение этой памяти.Строка живет до тех пор, пока живет узел.В этом случае используйте malloc и strcpy, чтобы создать строку для каждого узла.Не забудьте free строку, когда узел разрушен.Это наиболее распространенный сценарий и, вероятно, то, что вам нужно.

  2. Узлу не принадлежит собственная строка, но он указывает на внешнюю строку.Не разрешается ни выделять, ни освобождать память для этой строки.Есть другая сущность, ответственная за управление временем жизни этой строки и обеспечение того, что строка жива, по крайней мере, так как узел жив.Строка может пережить узел без утечки памяти.

Например, рассмотрим следующий сценарий:

  • существует список R строковых ресурсов.Этот список владеет этими ресурсами.Этот список будет использовать сценарий 1
  • , нам нужно сохранить два разных порядка сортировки R. Таким образом, у нас есть два списка A и B, которые будут использовать сценарий 2: каждый узел в A и B просто указывает на строку R.
...