В чем разница между массивом char и указателем char в C?

Я пытаюсь понять указатели в C, но в настоящее время я путаю со следующим:

  • char *p = "hello"
    

    это указатель char, указывающий на массив символов, начиная с h.

  • char p[] = "hello"
    

    - это массив, который хранит привет.

в чем разница, когда я передаю обе эти переменные в эту функцию?

void printSomething(char *p)
{
    printf("p: %s",p);
}

7 ответов


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

ваш пример функции printSomething ожидает указатель, поэтому, если вы попытаетесь передать ему массив, как это:

char s[10] = "hello";
printSomething(s);

компилятор делает вид, что вы написали это:

char s[10] = "hello";
printSomething(&s[0]);

давайте посмотрим:

#include <stdio.h>
#include <string.h>

int main()
{
    char *p = "hello";
    char q[] = "hello"; // no need to count this

    printf("%zu\n", sizeof(p)); // => size of pointer to char -- 4 on x86, 8 on x86-64
    printf("%zu\n", sizeof(q)); // => size of char array in memory -- 6 on both

    // size_t strlen(const char *s) and we don't get any warnings here:
    printf("%zu\n", strlen(p)); // => 5
    printf("%zu\n", strlen(q)); // => 5

    return 0;
}

foo* и foo [] - это разные типы, и они обрабатываются компилятором по-разному (указатель = адрес + представление типа указателя, массив = указатель + необязательная длина массива, если известно, например, если массив статически выделен), подробности можно найти в стандарте. И на уровне выполнения никакой разницы между ними нет (в ассемблере, ну, почти, см. ниже).

кроме того, есть обзоры вопрос на C FAQ:

Q: в чем разница между этими инициализациями?

char a[] = "string literal";   
char *p  = "string literal";   

моя программа аварийно завершает работу, если я пытаюсь присвоить новое значение p[i].

A: строковый литерал (формальный термин для строки с двойными кавычками в источнике C) может использоваться двумя немного разными способами:

  1. как инициализатор для массива char, как в объявление char a [] указывает начальные значения символов в этом массиве (и, при необходимости, его размер).
  2. в любом другом месте он превращается в безымянный статический массив символов, и этот безымянный массив может храниться в памяти только для чтения, и поэтому он не может быть обязательно изменен. В контексте выражения, массив преобразуется в указатель, как обычно (см. раздел 6), поэтому второе объявление инициализирует P в момент первого безымянного массива элемент.

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

см. также вопросы 1.31, 6.1, 6.2, 6.8, и 11.8 б.

ссылки: K & R2 SEC. 5.5 p. 104

ИСО, П. 6.1.4, П. 6.5.7

Обоснование Раздел 3.1.4

H&с п. 2.7.4 стр. 31-2


С99 проект N1256

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

  1. инициализации char[]:

    char c[] = "abc";      
    

    это "больше магии", и описанного в 6.7.8/14 "инициализация":

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

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

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

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

  2. везде еще: он генерирует:

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

    char *c = "abc";
    

    это похоже на:

    /* __unnamed is magic because modifying it gives UB. */
    static char __unnamed[] = "abc";
    char *c = __unnamed;
    

    обратите внимание на неявное приведение от char[] to 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', '' },
t[] = { 'a', 'b', 'c' };

содержимое массивов можно изменять. С другой стороны, декларация

char *p = "abc";

определяет p С типом " указатель на char "и инициализирует его, чтобы указать на объект с типом" массив char " длиной 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   x0,-0x8(%rbp)
f:  00 
        c: R_X86_64_32S .rodata

заключение: магазины GCC char* его в , не на .text.

если мы сделаем то же самое для char[]:

 char s[] = "abc";

получаем:

17:   c7 45 f0 61 62 63 00    movl   x636261,-0x10(%rbp)

поэтому он хранится в стеке (относительно %rbp).

обратите внимание, однако, что сценарий компоновщика по умолчанию ставит .rodata и .text в том же сегменте, который имеет разрешение execute, но без разрешения на запись. Это можно наблюдать с помощью:

readelf -l a.out

, который содержит:

 Section to Segment mapping:
  Segment Sections...
   02     .text .rodata

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


для таких случаев, а эффект тот же: вы в конечном итоге передает адрес первого символа в строке символов.

объявления, очевидно, не то же самое, хотя.

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

char *p = "hello";

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

char p[10] = "hello";

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

p[1]== *(&p+1)

является истинным утверждением


char p[3] = "hello" ? должно быть char p[6] = "hello" помните, что в конце "строки" В C.

в любом случае, массив в C-это просто указатель на первый объект объектов adjust в памяти. единственные разные s - в семантике. хотя вы можете изменить значение указателя, чтобы указать на другое место в памяти, массив после создания всегда будет указывать на одно и то же место.
также при использовании массива" new "и" delete " автоматически выполняются для вас.