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

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

  • char *p = "hello"
    

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

  • char p[] = "hello"
    

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

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

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

Ответ 1

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

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

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

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

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

Ответ 2

Посмотрим:

#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 [] - разные типы, и они обрабатываются по-разному с помощью компилятора (указатель = адрес + представление типа указателя, array = pointer + дополнительная длина массива, если известно, например, если массив статически выделяется), детали можно найти в стандарте. И на уровне времени выполнения разница между ними (в ассемблере, ну, почти, см. Ниже).

Кроме того, существует вопрос в C FAQ:

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

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

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

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

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

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

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

Ссылки: K & R2 Sec. 5,5 с. 104

Раздел ISO. 6.1.4, п. 6.5.7

Обоснование. 3.1.4

H & S Sec. 2.7.4 с. 31-2

Ответ 3

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

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 с типом "pointer to char" и инициализирует его, чтобы указать на объект с типом "array of char" длиной 4, элементы которого инициализируются литералом символьной строки. Если предпринята попытка использовать p для изменения содержимого массива, поведение не определено.

GCC 4.8 x86-64 реализация ELF

Программа:

#include <stdio.h>

int main(void) {
    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 в .rodata и тот же сегмент, который имеет исполняемый файл, но не имеет разрешения на запись. Это можно наблюдать с помощью:

readelf -l a.out

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

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

Ответ 4

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

Ответ 5

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

Объявления явно не совпадают, хотя.

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

char *p = "hello";

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

char p[10] = "hello";

Ответ 6

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

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

- истинное утверждение

Ответ 7

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

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