C: различия между указателем и массивом char
Рассмотрим:
char amessage[] = "now is the time";
char *pmessage = "now is the time";
Я прочитал из Язык программирования C, 2nd Edition, что вышеприведенные два утверждения не делают то же самое.
Я всегда думал, что массив - это удобный способ манипулирования указателями для хранения некоторых данных, но это явно не так... Каковы "нетривиальные" различия между массивами и указателями в C?
Ответы
Ответ 1
Правда, но это тонкая разница. По существу, первый:
char amessage[] = "now is the time";
Определяет массив, члены которого живут в текущем пространстве стека области, тогда как:
char *pmessage = "now is the time";
Определяет указатель, который живет в текущем пространстве стека области, но эта ссылка ссылается на память в другом месте (в этом, "сейчас время", хранится в другом месте в памяти, обычно в таблице строк).
Кроме того, обратите внимание, что поскольку данные, принадлежащие ко второму определению (явный указатель), не хранятся в текущем пространстве стека области, он не определен точно, где он будет сохранен и не должен быть изменен.
Изменить: Как отметил Марк, GMan и Pavel, также существует разница, когда адрес-оператор используется для любой из этих переменных. Например, & pmessage возвращает указатель типа char ** или указатель на указатель на символы, тогда как & amessage возвращает указатель типа char (*) [16] или указатель на массив из 16 символов (который, как и char **, должен быть разыменован дважды по мере того, как указывает лампочка).
Ответ 2
Здесь представлена гипотетическая карта памяти, показывающая результаты двух объявлений:
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-элементный массив char по адресу памяти 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;
то он может использоваться везде, где можно использовать amessage.
Ответ 3
Массив содержит элементы. Указатель указывает на них.
Первая - это короткая форма выражения
char amessage[16];
amessage[0] = 'n';
amessage[1] = 'o';
...
amessage[15] = '\0';
То есть, это массив, содержащий все символы. Специальная инициализация инициализирует его для вас и автоматически определяет его размер. Элементы массива изменяемы - вы можете перезаписать в нем символы.
Вторая форма - это указатель, который просто указывает на символы. Он хранит символы не напрямую. Поскольку массив является строковым литералом, вы не можете взять указатель и записать туда, где он указывает
char *pmessage = "now is the time";
*pmessage = 'p'; /* undefined behavior! */
Этот код, вероятно, сработает на вашем поле. Но он может делать все, что ему нравится, потому что его поведение undefined.
Ответ 4
Я не могу добавить полезные ответы на другие ответы, но я замечу, что в Deep C Secrets Питер ван дер Линден описывает этот пример в деталях. Если вы задаете такие вопросы, я думаю, вам понравится эта книга.
P.S. Вы можете присвоить новое значение pmessage
. Вы не можете назначить новое значение amessage
; он неизменен.
Ответ 5
Если массив определен так, чтобы его размер был доступен во время объявления, sizeof(p)/sizeof(type-of-array)
вернет количество элементов в массиве.
Ответ 6
Наряду с памятью для строки "сейчас время" выделяется в двух разных местах, вы также должны иметь в виду, что имя массива действует как значение указателя, а не переменная указателя, которая имеет значение 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
Ответ 7
Первая форма (amessage
) определяет переменную (массив), которая содержит копию строки "now is the time"
.
Вторая форма (pmessage
) определяет переменную (указатель), которая живет в другом месте, чем любая копия строки "now is the time"
.
Попробуйте эту программу:
#include <inttypes.h>
#include <stdio.h>
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;
}
Вы увидите, что в то время как &amessage
равно &amessage[0]
, это неверно для &pmessage
и &pmessage[0]
. Фактически, вы увидите, что строка, хранящаяся в amessage
, живет в стеке, а строка, на которую указывает pmessage
, живет в другом месте.
Последний printf показывает адрес строкового литерала. Если ваш компилятор делает "строковый пул", тогда будет только одна копия строки "now is the time" - и вы увидите, что ее адрес не совпадает с адресом amessage
. Это связано с тем, что amessage
получает копию строки при ее инициализации.
В конце концов, точка в том, что amessage
хранит строку в своей собственной памяти (в стеке в этом примере), а pmessage
указывает на строку, которая хранится в другом месте.
Ответ 8
Указатель - это просто переменная, содержащая адрес памяти. Обратите внимание, что вы играете с "строковыми литералами", что является другой проблемой. Различия в пояснении: В принципе:
#include <stdio.h>
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;
}
Ответ 9
Второй выделяет строку в некоторой секции только для чтения ELF.
Попробуйте следующее:
#include <stdio.h>
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').
Ответ 10
различия между указателем и массивом символов
C99 N1256 черновик
Существует два совершенно разных применения литералов массива:
-
Инициализировать char[]
:
char c[] = "abc";
Это "более волшебное" и описано в 6.7.8/14 "Инициализация":
Массив типа символа может быть инициализирован символьным строковым литералом, необязательно заключенным в фигурные скобки. Последовательные символы символьного строкового литерала (включая завершающий нулевой символ, если есть место или массив неизвестного размера) инициализируют элементы массива.
Так что это просто ярлык для:
char c[] = {'a', 'b', 'c', '\0'};
Как и любой другой регулярный массив, c
может быть изменен.
-
Всюду: генерирует:
Поэтому, когда вы пишете:
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 <stdio.h>
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
Ответ 11
Вышеуказанные ответы должны были ответить на ваш вопрос. Но я хотел бы предложить вам прочитать абзац "Embryonic C" в "Развитие языка C" , автором которого является сэр Деннис Ритчи.
Ответ 12
Для этой строки: char amessage [] = "теперь время";
компилятор будет оценивать использование amessage как указателя на начало массива с символами "сейчас время". Компилятор выделяет память для "now is the time" и инициализирует ее строкой "now is the time". Вы знаете, где это сообщение хранится, потому что сообщение всегда относится к началу этого сообщения. amessage не может быть присвоено новое значение - это не переменная, это имя строки "now is time".
Эта строка: char * pmessage = "теперь время";
объявляет переменную, pmessage, которая инициализируется (с учетом начального значения) начального адреса строки "now is time". В отличие от amessage, pmessage можно получить новое значение. В этом случае, как и в предыдущем случае, компилятор также сохраняет "сейчас время" в другом месте в памяти.
Например, это заставит pmessage указывать на "i", который начинается "время". pmessage = pmessage + 4;
Ответ 13
Вот мое резюме ключевых различий между массивами и указателями, которые я сделал для себя:
//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.
Ответ 14
Массив - это указатель на const. Вы не можете обновить его значение и сделать его точкой в другом месте.
Хотя для указателя вы можете сделать.