C путаница строк

Я изучаю C прямо сейчас и немного запутался с массивами символов - строками.

char name[15]="Fortran";

Нет проблем с этим - его массив, который может удерживать (до?) 15 символов

char name[]="Fortran";

C подсчитывает количество символов для меня, поэтому мне не нужно - аккуратно!

char* name;

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

  • Почему они называют это указателем char? Я знаю указатели как ссылки на переменные
  • Является ли это "оправданием"? Это находит другое использование, чем в char *?
  • Что это на самом деле? Это указатель? Как вы используете его правильно?

спасибо заранее, ламы

Ответ 1

Я думаю, это можно объяснить таким образом, так как картинка стоит тысячи слов...

Мы начнем с char name[] = "Fortran", который представляет собой массив символов, длина которого известна во время компиляции, 7, если быть точным, правильно? Неправильно! это 8, так как '\ 0' является нулевым завершающим символом, все строки должны иметь это.

char name[] = "Fortran";
+======+     +-+-+-+-+-+-+-+--+
|0x1234|     |F|o|r|t|r|a|n|\0|
+======+     +-+-+-+-+-+-+-+--+ 

В момент времени компилятор и компоновщик дали символу name адрес памяти 0x1234. Например, используя индексный оператор, т.е. name[1], компилятор знает, как вычислить, где в памяти символ с смещением, 0x1234 + 1 = 0x1235, и это действительно "o". Это достаточно просто, кроме того, с стандартом ANSI C размер типа char равен 1 байт, что может объяснить, как среда выполнения может получить значение этого семантического name[cnt++], предполагая, что cnt является int eger и, например, имеет значение 3, время выполнения автоматически увеличивается на один, а отсчет с нуля - значение "t". Это просто так хорошо.

Что произойдет, если был выполнен name[12]? Ну, код либо сбой, либо вы получите мусор, так как граница массива от индекса/смещения 0 (0x1234) до 8 (0x123B). Все, что после этого не принадлежит переменной name, которая будет называться переполнением буфера!

Адрес name в памяти равен 0x1234, как в примере, если вы должны это сделать:

printf("The address of name is %p\n", &name);

Output would be:
The address of name is 0x00001234

Для краткости и соблюдения примера адреса памяти 32 бит, поэтому вы видите дополнительные 0. Справедливо? Правильно, давайте двигаться дальше.

Теперь указатели... char *name является указателем на тип char....

Edit: И мы инициализируем его до NULL, как показано Спасибо Dan за то, что указали небольшую ошибку...

char *name = (char*)NULL;
+======+     +======+ 
|0x5678| ->  |0x0000|    ->    NULL
+======+     +======+ 

При компиляции/времени ссылки name не указывает ни на что, но имеет адрес времени компиляции/ссылки для символа name (0x5678), на самом деле это NULL, адрес указателя name неизвестно, следовательно, 0x0000.

Теперь, помните, это важно, адрес символа известен в момент компиляции/ссылки, но адрес указателя неизвестен при работе с указателями любого типа

Предположим, что мы делаем это:

name = (char *)malloc((20 * sizeof(char)) + 1);
strcpy(name, "Fortran");

Мы вызвали malloc для выделения блока памяти на 20 байтов, нет, это не 21, причина, по которой я добавил 1 к размеру, для символа "\ 0" nul. Предположим, что во время выполнения указанный адрес был 0x9876,

char *name;
+======+     +======+          +-+-+-+-+-+-+-+--+
|0x5678| ->  |0x9876|    ->    |F|o|r|t|r|a|n|\0|
+======+     +======+          +-+-+-+-+-+-+-+--+

Итак, когда вы это сделаете:

printf("The address of name is %p\n", name);
printf("The address of name is %p\n", &name);

Output would be:
The address of name is 0x00005678
The address of name is 0x00009876

Теперь вот здесь возникает иллюзия, что "массивы и указатели одинаковы" здесь

Когда мы это делаем:

char ch = name[1];

Что происходит во время выполнения:

  • Отображается адрес символа name.
  • Получить адрес памяти этого символа, то есть 0x5678.
  • В этом адресе содержится другой адрес, адрес указателя на память и выборка, т.е. 0x9876
  • Получить смещение на основе значения индекса 1 и добавить его на адрес указателя, то есть 0x9877, чтобы получить значение по этому адресу памяти, то есть "o" и присваивается ch.

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

Помните, массив типа T всегда будет распадаться на указатель первого элемента типа T.

Когда мы это делаем:

char ch = *(name + 5);
  • Отображается адрес символа name.
  • Получить адрес памяти этого символа, то есть 0x5678.
  • В этом адресе содержится другой адрес, адрес указателя на память и выборка, т.е. 0x9876
  • Получите смещение на основе значения 5 и добавьте его на адрес указателя, то есть 0x987A, чтобы получить значение по этому адресу памяти, то есть "r" и присваивается ch.

Кстати, вы также можете сделать это с массивом символов...

Далее, используя индексы в контексте массива, т.е. char name[] = "..."; и name[subscript_value], действительно то же самое, что и * (name + subscript_value). то есть.

name[3] is the same as *(name + 3)

И поскольку выражение *(name + subscript_value) коммутативное, то есть в обратном порядке,

*(subscript_value + name) is the same as *(name + subscript_value)

Следовательно, это объясняет, почему в одном из ответов выше вы можете написать это так (несмотря на это, практика не рекомендуется, даже если она вполне законна!)

3[name]

Хорошо, как мне получить значение указателя? Для этого используется * Предположим, что указатель name имеет этот адрес памяти указателя 0x9878, опять же, ссылаясь на приведенный выше пример, вот как это достигается:

char ch = *name;

Это означает, что получить значение, на которое указывает адрес памяти 0x9878, теперь ch будет иметь значение "r". Это называется разыменование. Мы просто разыменовали указатель name, чтобы получить значение и назначить его ch.

Кроме того, компилятор знает, что a sizeof(char) равно 1, поэтому вы можете делать операции увеличения/уменьшения указателя, такие как

*name++;
*name--;

Указатель автоматически активирует вверх/вниз в результате одним.

Когда мы это сделаем, предположим адрес памяти указателя 0x9878:

char ch = *name++;

Что такое значение * name и какой адрес, ответ: *name теперь будет содержать 't' и назначить его ch, а адрес памяти указателя - 0x9879.

Здесь вы также должны быть осторожны в том же принципе и в духе, что и раньше, в отношении границ памяти в самой первой части (см. "Что произойдет, если имя [12] было выполнено" в выше) результаты будут одинаковыми, т.е. сбой кода и ожоги!

Теперь, что произойдет, если мы освободим блок памяти, на который указывает name, вызывая функцию C free с name как параметр, т.е. free(name):

+======+     +======+ 
|0x5678| ->  |0x0000|    ->    NULL
+======+     +======+ 

Да, блок памяти освобождается и возвращается в среду выполнения для использования другим выполнением следующего кода malloc.

Теперь здесь вступает в действие общая нотация Ошибка сегментации, так как name не указывает ни на что, что происходит, когда мы разыскиваем его i.e.

char ch = *name;

Да, код будет разбиваться и записываться с "Ошибка сегментации", это распространено в Unix/Linux. Под окнами появится диалоговое окно в строке "Неустранимая ошибка" или "Произошла ошибка с приложением, вы хотите отправить отчет в Microsoft?".... если указатель не был malloc d и любая попытка разыменовать его, гарантируется сбой и сжигание.

Также: помните, что для каждого malloc существует соответствующий free, если нет соответствующего free, у вас есть утечка памяти, в которой память выделена, но не освобождена.

И там у вас есть, вот как работают указатели и как массивы отличаются от указателей, если вы читаете учебник, в котором говорится, что они одинаковы, вырвите эту страницу и разорвите ее!:)

Надеюсь, это поможет вам в понимании указателей.

Ответ 2

Это указатель. Это означает, что это переменная, которая содержит адрес в памяти. Он "указывает" на другую переменную.

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

char* name = "Mr. Anderson";

На самом деле это примерно так же:

char name[] = "Mr. Anderson";

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

char *name;
name = malloc(256*sizeof(char));
strcpy(name, "This is less than 256 characters, so this is fine.");

В качестве альтернативы вы можете назначить его с помощью функции strdup(), например:

char *name;
name = strdup("This can be as long or short as I want.  The function will allocate enough space for the string and assign return a pointer to it.  Which then gets assigned to name");

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

if(name)
    free(name);
name = 0;

Обязательно проверьте, действительно ли это имя, прежде чем пытаться освободить его память. Это то, что делает оператор if.

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

Кроме того, указатели на символы удобны, поскольку они могут использоваться для указания на разные статически выделенные массивы символов. Вот так:

char *name;

char joe[] = "joe";
char bob[] = "bob";

name = joe;

printf("%s", name);

name = bob;
printf("%s", name);

Это часто случается, когда вы передаете статически выделенный массив функции, использующей указатель на символ. Например:

void strcpy(char *str1, char *str2);

Если вы затем передадите это:

char buffer[256];
strcpy(buffer, "This is a string, less than 256 characters.");

Он будет манипулировать обоими из них через str1 и str2, которые являются просто указателями, указывающими на то, где хранится буфер и строковый литерал.

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

char *myFunc() {
    char myBuf[64];
    strcpy(myBuf, "hi");
    return myBuf;
}

Это не сработает. В этом случае вы должны использовать указатель и выделить память (как показано выше). Затем выделенная память будет сохраняться, даже если вы выйдете из области функций. Просто не забудьте освободить его, как упоминалось ранее.

Это оказалось немного более энциклопедическим, чем я предполагал, надеюсь, что он поможет.

Отредактировано для удаления кода на С++. Я часто смешиваю их, иногда забываю.

Ответ 3

char * name - это просто указатель. Где-то вдоль строки памяти должно быть выделено и адрес этой памяти, сохраненный в имени.

  • Он может указывать на один байт памяти и быть "истинным" указателем на один char.
  • Он может указывать на непрерывную область памяти, которая содержит несколько символов.
  • Если эти символы заканчиваются нулевым терминатором, низко и созерцать, у вас есть указатель на строку.

Ответ 4

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

a[x] - это то же самое, что и *(a + x), т.е. разыменование указателя a, приращенного x.

если вы использовали следующее:

char foo[] = "foobar";
char bar = *foo;

bar будет установлен на 'f'

Чтобы предотвратить путаницу и избежать вводить в заблуждение людей, некоторые дополнительные слова о более сложной разнице между указателями и массивами, спасибо avakar:

В некоторых случаях указатель фактически семантически отличается от массива, (не исчерпывающий) список примеров:

//sizeof
sizeof(char*) != sizeof(char[10])

//lvalues
char foo[] = "foobar";
char bar[] = "baz";
char* p;
foo = bar; // compile error, array is not an lvalue
p = bar; //just fine p now points to the array contents of bar

// multidimensional arrays
int baz[2][2];
int* q = baz; //compile error, multidimensional arrays can not decay into pointer
int* r = baz[0]; //just fine, r now points to the first element of the first "row" of baz
int x = baz[1][1];
int y = r[1][1]; //compile error, don't know dimensions of array, so subscripting is not possible
int z = r[1]: //just fine, z now holds the second element of the first "row" of baz

И, наконец, веселая мелочь; поскольку a[x] эквивалентно *(a + x), вы можете фактически использовать, например. '3 [a]' для доступа к четвертому элементу массива a. То есть следующее является совершенно законным кодом и печатает 'b' четвертый символ строки foo.

#include <stdio.h>

int main(int argc, char** argv) {
  char foo[] = "foobar";

  printf("%c\n", 3[foo]);

  return 0;
}

Ответ 5

char *name, сам по себе не может содержать символы. Это важно.

char *name просто объявляет, что name является указателем (то есть переменной, значением которого является адрес), который будет использоваться для хранения адреса одного или нескольких символов в какой-то момент позже в программе. Однако он не выделяет в памяти какое-либо пространство для хранения этих символов и не гарантирует, что name даже содержит действительный адрес. Точно так же, если у вас есть объявление типа int number, нет способа узнать, что такое значение number, пока вы его явно не установили.

Так же, как после объявления значения целого числа, вы можете позже установить его значение (number = 42), после объявления указателя на char, вы можете позже установить его значение как допустимый адрес памяти, который содержит символ - или последовательность символов - что вас интересует.

Ответ 6

Это действительно запутанно. Важно понимать и различать то, что char name[] объявляет массив и char* name объявляет указатель. Они - разные животные.

Однако массив в C может быть неявно преобразован в указатель на его первый элемент. Это дает вам возможность выполнять арифметику указателей и выполнять итерацию по элементам массива (не имеет значения элементы какого типа, char или нет). Как уже упоминалось, вы можете использовать как оператор индексирования, так и арифметику указателя для доступа к элементам массива. Фактически, оператор индексирования - это просто синтаксический сахар (другое представление того же выражения) для арифметики указателя.

Важно различать разницу между массивом и указателем на первый элемент массива. Можно запросить размер массива, объявленного как char name[15], с помощью оператора sizeof:

char name[15] = { 0 };
size_t s = sizeof(name);
assert(s == 15);

но если вы примените sizeof к char* name, вы получите размер указателя на своей платформе (т.е. 4 байта):

char* name = 0;
size_t s = sizeof(name);
assert(s == 4); // assuming pointer is 4-bytes long on your compiler/machine

Кроме того, две формы определений массивов элементов char эквивалентны:

char letters1[5] = { 'a', 'b', 'c', 'd', '\0' };
char letters2[5] = "abcd"; /* 5th element implicitly gets value of 0 */

Двойной характер массивов, неявное преобразование массива в указатель на его первый элемент, в языке C (а также С++), указатель может использоваться как итератор для перемещения по элементам массива:

/ *skip to 'd' letter */
char* it = letters1;
for (int i = 0; i < 3; i++)
    it++;

Ответ 7

Один из них является реальным объектом массива, а другой является ссылкой или указателем на такой объект массива.

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

Разницу можно увидеть в значении &name. В первых двух случаях это то же значение, что и только name, но в третьем случае это другой тип, называемый указателем на указатель на char или **char, и это адрес самого указателя. То есть, это двунаправленный указатель.

#include <stdio.h>

char name1[] = "fortran";
char *name2 = "fortran";

int main(void) {
    printf("%lx\n%lx %s\n", (long)name1, (long)&name1, name1);
    printf("%lx\n%lx %s\n", (long)name2, (long)&name2, name2);
    return 0;
}
Ross-Harveys-MacBook-Pro:so ross$ ./a.out
100001068
100001068 fortran
100000f58
100001070 fortran