C: различия между указателем char и массивом
Рассматривать:
char amessage[] = "now is the time"; char *pmessage = "now is the time";
Я прочитал на языке программирования C , 2-е издание, что вышеупомянутые два утверждения не делают то же самое.
Я всегда думал, что массив – это удобный способ манипулирования указателями для хранения некоторых данных, но это явно не так … Каковы «нетривиальные» различия между массивами и указателями в C?
- Является ли NULL всегда равным нулю в C?
- Разница между * (указатель + указатель) и указатель
- Разница указателей / адресов
- Обращение строки в C
- Передача по ссылке в C
- Почему эта реализация offsetof () работает?
- разыменование нулевой указатель
- Что такое «->» в Objective C?
- C ++ Возвращаемый массив многомерности из функции
- Назначения типа «auto» указателя в c ++ 11 требуют «*»?
- Go, X не реализует Y (... метод имеет приемник указателя)
- Что такое умный указатель, и когда я должен его использовать?
- C ++ typedef интерпретация константных указателей
Правда, но это тонкая разница. По существу, первое:
char amessage[] = "now is the time";
Определяет массив, члены которого находятся в пространстве стека текущей области, тогда как:
char *pmessage = "now is the time";
Определяет указатель, который живет в пространстве стека текущей области, но эта ссылка ссылается на память в другом месте (в этом, «сейчас время», хранится в другом месте в памяти, обычно в таблице строк).
Также обратите внимание, что поскольку данные, принадлежащие второму определению (явный указатель), не хранятся в пространстве стека текущей области, он не определен точно, где он будет сохранен и не должен быть изменен.
Редактировать: Как отметил Марк, GMan и Pavel, также существует разница, когда адрес-оператор используется для любой из этих переменных. Например, & pmessage возвращает указатель типа char ** или указатель на указатель на символы, тогда как & amessage возвращает указатель типа char (*) [16] или указатель на массив из 16 символов (что, например, char **, необходимо разыменовать дважды, как указано на индикаторе).
Вот гипотетическая карта памяти, показывающая результаты двух объявлений:
0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x00008000: 'n' 'o' 'w' ' ' 'i' 's' ' ' 't' 0x00008008: 'h' 'e' ' ' 't' 'i' 'm' 'e' '\0' ... amessage: 0x00500000: 'n' 'o' 'w' ' ' 'i' 's' ' ' 't' 0x00500008: 'h' 'e' ' ' 't' 'i' 'm' 'e' '\0' pmessage: 0x00500010: 0x00 0x00 0x80 0x00
Строковый литерал «now is time» хранится в виде 16-элементного массива char по адресу памяти 0x00008000. Эта память может быть недоступна для записи; лучше предположить, что это не так. Вы никогда не должны пытаться модифицировать содержимое строкового литерала.
Декларация
char amessage[] = "now is the time";
выделяет 16-элементный массив символов по адресу памяти 0x00500000 и копирует содержимое строкового литерала. Эта память доступна для записи; вы можете изменить содержание сообщения на содержание вашего сердца:
strcpy(amessage, "the time is now");
Декларация
char *pmessage = "now is the time";
выделяет один указатель на char по адресу памяти 0x00500010 и копирует на него адрес строкового литерала.
Поскольку pmessage указывает на строковый литерал, он не должен использоваться в качестве аргумента для функций, которые нуждаются в изменении содержимого строки:
strcpy(amessage, pmessage); /* OKAY */ strcpy(pmessage, amessage); /* NOT OKAY */ strtok(amessage, " "); /* OKAY */ strtok(pmessage, " "); /* NOT OKAY */ scanf("%15s", amessage); /* OKAY */ scanf("%15s", pmessage); /* NOT OKAY */
и так далее. Если вы изменили сообщение pmessage, чтобы указать на сообщение:
pmessage = amessage;
то он может использоваться везде, где можно использовать сообщения.
Массив содержит элементы. Указатель указывает на них.
Первая – это короткая форма высказывания
char amessage[16]; amessage[0] = 'n'; amessage[1] = 'o'; ... amessage[15] = '\0';
То есть, это массив, содержащий все символы. Специальная инициализация инициализирует его для вас и автоматически определяет его размер. Элементы массива изменяемы – вы можете перезаписать в нем символы.
Вторая форма – это указатель, который просто указывает на символы. Он хранит символы не напрямую. Поскольку массив является строковым литералом, вы не можете взять указатель и записать туда, где он указывает
char *pmessage = "now is the time"; *pmessage = 'p'; /* undefined behavior! */
Этот код, вероятно, упадет на ваш ящик. Но он может делать все, что ему нравится, потому что его поведение не определено.
Я не могу добавить полезные ответы на другие ответы, но я хочу заметить, что в Deep C Secrets Питер ван дер Линден подробно описывает этот пример. Если вы задаете такие вопросы, я думаю, вам понравится эта книга.
PS Вы можете назначить новое значение для pmessage
. Вы не можете назначить новое значение для amessage
; он неизменен .
Если массив определен так, чтобы его размер был доступен во время объявления, sizeof(p)/sizeof(type-of-array)
вернет количество элементов в массиве.
Наряду с памятью для строки «сейчас время» выделяется в двух разных местах, вы также должны иметь в виду, что имя массива действует как значение указателя, а не переменная указателя, которая имеет значение pmessage. Основное отличие состоит в том, что переменную указателя можно изменить, чтобы указать где-то в другом месте, и массив не может.
char arr[] = "now is the time"; char *pchar = "later is the time"; char arr2[] = "Another String"; pchar = arr2; //Ok, pchar now points at "Another String" arr = arr2; //Compiler Error! The array name can be used as a pointer VALUE //not a pointer VARIABLE
Указатель – это просто переменная, содержащая адрес памяти. Обратите внимание, что вы играете с «строковыми литералами», что является другой проблемой. Различия в пояснении: В основном:
#include int main () { char amessage[] = "now is the time"; /* Attention you have created a "string literal" */ char *pmessage = "now is the time"; /* You are REUSING the string literal */ /* About arrays and pointers */ pmessage = NULL; /* All right */ amessage = NULL; /* Compilation ERROR!! */ printf ("%d\n", sizeof (amessage)); /* Size of the string literal*/ printf ("%d\n", sizeof (pmessage)); /* Size of pmessage is platform dependent - size of memory bus (1,2,4,8 bytes)*/ printf ("%p, %p\n", pmessage, &pmessage); /* These values are different !! */ printf ("%p, %p\n", amessage, &amessage); /* These values are THE SAME!!. There is no sense in retrieving "&amessage" */ /* About string literals */ if (pmessage == amessage) { printf ("A string literal is defined only once. You are sharing space"); /* Demostration */ "now is the time"[0] = 'W'; printf ("You have modified both!! %s == %s \n", amessage, pmessage); } /* Hope it was useful*/ return 0; }
Первая форма ( amessage
) определяет переменную (массив), которая содержит копию строки "now is the time"
.
Вторая форма ( pmessage
) определяет переменную (указатель), которая живет в другом месте, чем любая копия строки "now is the time"
.
Попробуйте эту программу:
#include #include int main (int argc, char *argv []) { char amessage [] = "now is the time"; char *pmessage = "now is the time"; printf("&amessage : %#016"PRIxPTR"\n", (uintptr_t)&amessage); printf("&amessage[0]: %#016"PRIxPTR"\n", (uintptr_t)&amessage[0]); printf("&pmessage : %#016"PRIxPTR"\n", (uintptr_t)&pmessage); printf("&pmessage[0]: %#016"PRIxPTR"\n", (uintptr_t)&pmessage[0]); printf("&\"now is the time\": %#016"PRIxPTR"\n", (uintptr_t)&"now is the time"); return 0; }
Вы увидите, что while &amessage
равно &amessage[0]
, это неверно для &pmessage
и &pmessage[0]
. Фактически, вы увидите, что строка, хранящаяся в amessage
живет в стеке, а строка, на которую pmessage
живет в другом месте.
Последний printf показывает адрес строкового литерала. Если ваш компилятор делает «строковый пул», тогда будет только одна копия строки «сейчас время» – и вы увидите, что ее адрес не совпадает с адресом amessage
. Это связано с тем, что при его инициализации экземпляр экземпляра получает копию строки.
В конце концов, дело в том, что amessage
сохраняет строку в своей собственной памяти (в стеке в этом примере), а pmessage
указывает на строку, которая хранится в другом месте.
Второй выделяет строку в некоторой секции только для чтения ELF. Попробуйте следующее:
#include int main(char argc, char** argv) { char amessage[] = "now is the time"; char *pmessage = "now is the time"; amessage[3] = 'S'; printf("%s\n",amessage); pmessage[3] = 'S'; printf("%s\n",pmessage); }
и вы получите segfault по второму назначению (pmessage [3] = ‘S’).
различия между указателем и массивом символов
C99 N1256 черновик
Существует два совершенно разных применения литералов массива:
-
Инициализировать
char[]
:char c[] = "abc";
Это «более волшебное» и описано в 6.7.8 / 14 «Инициализация» :
Массив типа символа может быть инициализирован символьным строковым литералом, необязательно заключенным в фигурные скобки. Последовательные символы символьного строкового литерала (включая завершающий нулевой символ, если есть место или массив неизвестного размера) инициализируют элементы массива.
Так что это просто ярлык для:
char c[] = {'a', 'b', 'c', '\0'};
Как и любой другой регулярный массив,
c
может быть изменен. -
Всюду: генерирует:
- неназванный
- array of char Каков тип строковых литералов в C и C ++?
- со статическим хранилищем
- который дает UB, если он изменен
Поэтому, когда вы пишете:
char *c = "abc";
Это похоже на:
/* __unnamed is magic because modifying it gives UB. */ static char __unnamed[] = "abc"; char *c = __unnamed;
Обратите внимание на неявный перевод из
char[]
вchar *
, который всегда легален.Затем, если вы измените
c[0]
, вы также измените__unnamed
, что является UB.Это описано в разделе 6.4.5 «Строковые литералы» :
5 В фазе 7 перевода байт или код нулевого значения добавляется к каждой многобайтовой последовательности символов, которая получается из строкового литерала или литералов. Последовательность многобайтовых символов затем используется для инициализации массива статической продолжительности хранения и длины, достаточной для того, чтобы содержать последовательность. Для символьных строковых литералов элементы массива имеют тип char и инициализируются отдельными байтами многобайтовой последовательности символов […]
6 Неизвестно, являются ли эти массивы различными, если их элементы имеют соответствующие значения. Если программа пытается изменить такой массив, поведение не определено.
6.7.8 / 32 «Инициализация» дает прямой пример:
ПРИМЕР 8: Декларация
char s[] = "abc", t[3] = "abc";
определяет «простые» объекты массива символов
s
иt
, элементы которых инициализируются символами строковых символов.Эта декларация идентична
char s[] = { 'a', 'b', 'c', '\0' }, t[] = { 'a', 'b', 'c' };
Содержимое массивов может быть изменено. С другой стороны, декларация
char *p = "abc";
определяет
p
с типом «указатель на символ» и инициализирует его, указывая на объект с типом «массив символов» с длиной 4, элементы которого инициализируются символьным строковым литералом. Если для изменения содержимого массива сделана попытка использоватьp
, поведение не определено.
GCC 4.8 x86-64 Реализация ELF
Программа:
#include int main() { char *s = "abc"; printf("%s\n", s); return 0; }
Компилировать и декомпилировать:
gcc -ggdb -std=c99 -c main.c objdump -Sr main.o
Выход содержит:
char *s = "abc"; 8: 48 c7 45 f8 00 00 00 movq $0x0,-0x8(%rbp) f: 00 c: R_X86_64_32S .rodata
Вывод: GCC хранит char*
в разделе .rodata
, а не в .text
.
Если мы сделаем то же самое для char[]
:
char s[] = "abc";
мы получаем:
17: c7 45 f0 61 62 63 00 movl $0x636261,-0x10(%rbp)
поэтому он хранится в стеке (относительно %rbp
).
Обратите внимание, однако, что сценарий компоновщика по умолчанию ставит .rodata
и .text
в том же сегменте, который выполняет, но не имеет права на запись. Это можно наблюдать с помощью:
readelf -l a.out
который содержит:
Section to Segment mapping: Segment Sections... 02 .text .rodata
Вышеуказанные ответы должны были ответить на ваш вопрос. Но я хотел бы предложить вам прочитать параграф «Эмбриональный С» в «Развитии языка С», автором которого является сэр Деннис Ритчи.
Для этой строки: char amessage [] = «сейчас время»;
компилятор будет оценивать использование ассемблирования как указателя на начало массива с символами «сейчас время». Компилятор выделяет память для «now is the time» и инициализирует ее строкой «now is the time». Вы знаете, где это сообщение хранится, потому что сообщение всегда относится к началу этого сообщения. amessage не может быть присвоено новое значение – это не переменная, это имя строки «сейчас время».
Эта строка: char * pmessage = “сейчас время”;
объявляет переменную, pmessage, которая инициализируется (с учетом начального значения) начального адреса строки «now is time». В отличие от amessage, pmessage можно получить новое значение. В этом случае, как и в предыдущем случае, компилятор также сохраняет «сейчас время» в другом месте в памяти. Например, это заставит pmessage указывать на «i», который начинается «время». pmessage = pmessage + 4;
Вот мое резюме основных различий между массивами и указателями, которые я сделал для себя:
//ATTENTION: //Pointer depth 1 int marr[] = {1,13,25,37,45,56}; // array is implemented as a Pointer TO THE FIRST ARRAY ELEMENT int* pmarr = marr; // don't use & for assignment, because same pointer depth. Assigning Pointer = Pointer makes them equal. So pmarr points to the first ArrayElement. int* point = (marr + 1); // ATTENTION: moves the array-pointer in memory, but by sizeof(TYPE) and not by 1 byte. The steps are equal to the type of the array-elements (here sizeof(int)) //Pointer depth 2 int** ppmarr = &pmarr; // use & because going one level deeper. So use the address of the pointer. //TYPES //array and pointer are different, which can be seen by checking their types std::cout << "type of marr is: " << typeid(marr).name() << std::endl; // int* so marr gives a pointer to the first array element std::cout << "type of &marr is: " << typeid(&marr).name() << std::endl; // int (*)[6] so &marr gives a pointer to the whole array std::cout << "type of pmarr is: " << typeid(pmarr).name() << std::endl; // int* so pmarr gives a pointer to the first array element std::cout << "type of &pmarr is: " << typeid(&pmarr).name() << std::endl; // int** so &pmarr gives a pointer to to pointer to the first array elelemt. Because & gets us one level deeper.
Массив является указателем const. Вы не можете обновить его значение и сделать его точкой в другом месте. Хотя для указателя вы можете сделать.