Как работают строки и массивы символов в C?

Никакие проводники, которые я видел, как представляется, не объясняют это очень хорошо.

Я имею в виду, что вы можете выделить память для char* или записать char[25] вместо этого? Какая разница? А потом есть литералы, которыми нельзя манипулировать? Что делать, если вы хотите назначить фиксированную строку переменной? Например, stringVariable = "thisIsALiteral" , тогда как вы манипулируете им потом?

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


EDIT: Реальная проблема заключается в том, что, насколько я понимаю, вам нужно манипулировать этими разными конструкциями, чтобы выполнить даже простые вещи. Например, только char * может быть передан как аргумент или возвращаемое значение, но только char[] может быть назначен литералом и изменен. Я чувствую, что очевидно, что мы часто / всегда должны быть в состоянии сделать то и другое, и это то, где моя ловушка.

В чем разница между выделенным char* и char[25] ?

Время жизни строки в malloc не ограничено областью ее декларации. На простом языке вы можете вернуть строку malloc из функции; вы не можете сделать то же самое с char[25] выделенным в автоматическом хранилище , потому что его память будет возвращена после возврата из функции.

Можно ли манипулировать литералами?

Строковые литералы нельзя манипулировать на месте, поскольку они выделяются в хранилище только для чтения. Вам нужно скопировать их в изменяемое пространство, например, статическое, автоматическое или динамическое, чтобы ими управлять. Это невозможно сделать:

 char *str = "hello"; str[0] = 'H'; // <<== WRONG! This is undefined behavior. 

Это будет работать:

 char str[] = "hello"; str[0] = 'H'; // <<=== This is OK 

Это тоже работает:

 char *str = malloc(6); strcpy(str, "hello"); str[0] = 'H'; // <<=== This is OK too 

Как вы позаботились о нулевом завершении строковых литералов?

C компилятор заботится о нулевом завершении для вас: все строковые литералы имеют дополнительный символ в конце, заполненный \0 .

Ваш вопрос относится к трем различным конструкциям в массивах C: char, указателях символов, выделенных в куче, и строковых литералах. Все это разные тонкие способы.

  • Char, который вы получаете, объявляя char foo[25] внутри функции, эта память выделяется в стеке, она существует только в пределах области, которую вы объявили, но для вас было выделено ровно 25 байт. Вы можете хранить все, что захотите, в этих байтах, но если вы хотите строку, не забудьте использовать последний байт для нуль-завершения.

  • Указатели символов, определенные с помощью char *bar содержат указатель на нераспределенную память. Чтобы использовать их, вам нужно указать их на что-то, либо на массив, как раньше ( bar = foo ), либо выделить пробел bar = malloc(sizeof(char) * 25); , Если вы сделаете последнее, вы должны в конечном итоге освободить место.

  • Строковые литералы ведут себя по-разному в зависимости от того, как вы их используете. Если вы используете их для инициализации char char s[] = "String"; то вы просто объявляете массив, достаточно большой, чтобы точно удерживать эту строку (и нулевой терминатор) и помещать туда эту строку. Это то же самое, что и объявление массива char, а затем его заполнение.

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

Поскольку уже есть ответы на другие аспекты, я бы добавил только к вопросу « что, если вам нужна гибкость передачи функции с использованием char *, но изменчивость char []

Вы можете выделить массив и передать тот же массив функции, что и char *. Это называется pass by reference и внутренне только передает адрес фактического массива (именно адрес первого элемента) вместо копирования целого. Другой эффект заключается в том, что любое изменение, внесенное внутри функции, изменяет исходный массив.

 void fun(char *a) { a[0] = 'y'; // changes hello to yello } main() { char arr[6] = "hello"; // Note that its not char * arr fun(arr); // arr now contains yello } 

То же самое можно было бы сделать для массива, выделенного malloc

 char * arr = malloc(6); strcpy(arr, "hello"); fun(arr); // note that fun remains same. 

Позднее вы можете освободить память malloc

 free(arr); 

char * a, является просто указателем, который может хранить адрес, который может быть одной переменной или может быть первым элементом массива. Будьте ware, мы должны назначить этот указатель, прежде чем использовать его.

Вопреки тому, что char arr [SIZE] создает массив в стеке, он также выделяет байты SIZE. Таким образом, вы можете напрямую получить доступ к arr [3] (при условии, что 3 меньше SIZE) без каких-либо проблем.

Теперь имеет смысл разрешить присвоение любого адреса a, но не разрешать это для arr , поскольку нет другого способа, кроме использования arr для доступа к его памяти.