C: различия между указателем char и массивом

Рассматривать:

char amessage[] = "now is the time"; char *pmessage = "now is the time"; 

Я прочитал на языке программирования C , 2-е издание, что вышеупомянутые два утверждения не делают то же самое.

Я всегда думал, что массив – это удобный способ манипулирования указателями для хранения некоторых данных, но это явно не так … Каковы «нетривиальные» различия между массивами и указателями в C?

Правда, но это тонкая разница. По существу, первое:

 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 черновик

Существует два совершенно разных применения литералов массива:

  1. Инициализировать char[] :

     char c[] = "abc"; 

    Это «более волшебное» и описано в 6.7.8 / 14 «Инициализация» :

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

    Так что это просто ярлык для:

     char c[] = {'a', 'b', 'c', '\0'}; 

    Как и любой другой регулярный массив, c может быть изменен.

  2. Всюду: генерирует:

    • неназванный
    • 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. Вы не можете обновить его значение и сделать его точкой в ​​другом месте. Хотя для указателя вы можете сделать.

  • Почему ссылки не переустанавливаются в C ++
  • Какова точка указателей на функции?
  • Можно ли рассматривать 2D-массив как непрерывный массив 1D?
  • char pointer запутанный с cout в c ++
  • почему имя массива является указателем на первый элемент массива?
  • Почему массивы в C распадаются на указатели?
  • Как включить динамический массив INSIDE a struct в C?
  • Как направить себя в UnsafeMutablePointer type в swift
  • В чем разница между char a =? String ?; и char * p =? string?;?
  • C ++: безопасно ли указывать указатель на int и позже на указатель?
  • Почему для нулевого указателя используется нулевой адрес?
  • Interesting Posts

    Использовать неподписанный драйвер в Windows 7 x64

    Различия между unique_ptr и shared_ptr

    Как назначить псевдоним имени функции в C ++?

    Проверьте, установлен ли флажок с помощью jQuery

    jQuery select на основе текста

    Как определить последний ряд, используемый в VBA, включая пробелы между

    Перемещение левого бита 255 (в виде байта)

    Какую страtagsю вы используете для именования пакетов в проектах Java и почему?

    Повторное изменение ширины до нескольких с несколькими столбцами значений

    SVM-classификатор на основе функций HOG для «обнаружения объекта» в OpenCV

    Создание пользовательского editText с функцией тегов

    Перекрывающиеся совпадения в R

    Изменение цвета экрана Android 5.0

    Представления, наложенные выше YouTubePlayerFragment или YouTubePlayerView в иерархии компоновки, приводят к тому, что воспроизведение приостанавливается немедленно

    Как включить / выключить флэш-память iPhone?

    Давайте будем гением компьютера.