Беспорядок струн до

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

char name[15]="Fortran";

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

char name[]="Fortran";

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

char* name;

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

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

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

19
задан lamas 23 January 2010 в 22:13
поделиться

7 ответов

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

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

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

Во время компоновки компилятор и компоновщик присвоили символу name адрес памяти 0x1234. Используя оператор subscript, то есть, например, name[1], компилятор знает, как вычислить, где в памяти находится символ по смещению, 0x1234 + 1 = 0x1235, и это действительно 'o'. Это достаточно просто, более того, в стандарте ANSI C размер типа данных char равен 1 байту, что может объяснить, как время выполнения может получить значение этого семантического name[cnt++], предполагая, что cnt является integer и имеет значение, например, 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, как показано Спасибо Дэн за указание на небольшую ошибку...

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];

Во время выполнения происходит следующее:

  1. Ищется адрес символа name
  2. Получаем адрес памяти этого символа, т.е. 0x5678.
  3. По этому адресу находится другой адрес, адрес указателя на память, и извлекается он, т.е. 0x9876
  4. Получаем смещение, основанное на значении подписи 1, и добавляем его к адресу указателя, т.е. 0x9877, чтобы получить значение по этому адресу памяти, т.е. 'o', которое присваивается ch.

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

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

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

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

Кстати, так же можно поступить и с массивом символов...

Более того, используя операторы subscript в контексте массива, т.е. 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.

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

*name++;
*name--;

В результате указатель автоматически увеличивается/уменьшается на единицу.

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

char ch = *name++;

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

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

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

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

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

Теперь, здесь вступает в игру общепринятая нотация Segmentation fault, поскольку name ни на что не указывает, что произойдет, когда мы разыменуем его, т.е.

char ch = *name;

Да, код рухнет и сгорит с "Segmentation fault", это обычное явление в Unix/Linux. В windows появится диалоговое окно типа 'Unrecoverable error' или 'An error has occurred with the application, do you wish to send the report to Microsoft?'.... если указатель не был mallocd и любая попытка разыменовать его, гарантированно приведет к краху и сгоранию.

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

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

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

33
ответ дан 30 November 2019 в 02:56
поделиться

Чар * имя , на свой собственный, не может держать какие-либо персонажи . Это важно.

имя CHAR * просто объявляет, что имя - указатель (то есть переменная, значение которой является адресом), который будет использоваться для хранения адреса одного или нескольких символов в какой-то момент позже в программе. Это, однако, не выделяет любое пространство в памяти, чтобы фактически удерживать эти символы, и не гарантирует, что имя даже содержит действительный адрес. Таким же образом, если у вас есть декларация, такая как int Number Нет способа узнать, какова стоимость номера , пока вы явно не установите его.

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

2
ответ дан 30 November 2019 в 02:56
поделиться

Из моих исследований, кажется, что взрыва .NET теперь был прекращен. Однако это , вероятно, поможет.

Обновление: 1 , похоже, требует платной компоненты, Это , вероятно, является лучшим выбором.

-121--4293907-

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

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

Разница видно в значении и имя . В первых двух случаях это одно и то же значение, что и только что имя , но в третьем случае это другой тип называется указатель на указатель для 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
1
ответ дан 30 November 2019 в 02:56
поделиться

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

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

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;

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

Причина, по которой вы видите указатели персонажей, используются в целом в 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;
}

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

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

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

3
ответ дан 30 November 2019 в 02:56
поделиться

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

  • Это может указывать на один байт памяти и быть «истинным» указателем на один символ.
  • Это может указать на непрерывную область памяти, которая содержит ряд символов.
  • Если эти персонажи случаются, чтобы закончиться нулевым терминатором, низким и вотму, у вас есть указатель на строку.
2
ответ дан 30 November 2019 в 02:56
поделиться

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

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

Важно различать разницу между массивом и указателем на первый элемент массива. Размер массива, объявленного как 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 (а также C++) указатель может быть использован в качестве итератора для прохождения по элементам массива:

/ *skip to 'd' letter */
char* it = letters1;
for (int i = 0; i < 3; i++)
    it++;
2
ответ дан 30 November 2019 в 02:56
поделиться

В 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) , вы действительно можете использовать E.g. '3 [A] для доступа к четвертому элементу массива A. Я Ниже приведен совершенно законный код, и будет печать «B» четвертый символ строки FOO.

#include <stdio.h>

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

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

  return 0;
}
2
ответ дан 30 November 2019 в 02:56
поделиться
Другие вопросы по тегам:

Похожие вопросы: