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

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

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

    char c[] = "abc";      
    

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

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

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

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

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

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

    Поэтому, когда вы пишете:

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