В чем разница между массивом 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) может использоваться двумя немного разными способами:
- как инициализатор для массива char, как в объявление char a [] указывает начальные значения символов в этом массиве (и, при необходимости, его размер).
- в любом другом месте он превращается в безымянный статический массив символов, и этот безымянный массив может храниться в памяти только для чтения, и поэтому он не может быть обязательно изменен. В контексте выражения, массив преобразуется в указатель, как обычно (см. раздел 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
существует два совершенно разных использования литералов массива:
-
инициализации
char[]
:char c[] = "abc";
это "больше магии", и описанного в 6.7.8/14 "инициализация":
массив символьного типа может быть инициализирован символьным строковым литералом, необязательно заключенный в скобки. Последовательные символы символьного строкового литерала (в том числе завершающий нулевой символ, если есть номер или если массив неизвестного размера) инициализировать элементы массива.
так что это просто ярлык для:
char c[] = {'a', 'b', 'c', ''};
как и любой другой обычный массив,
c
может быть изменен. -
везде еще: он генерирует:
- безымянный
- массив char какой тип строковых литералов в C и C++?
- со статическим хранением
- это дает UB при изменении
поэтому, когда вы пишете:
char *c = "abc";
это похоже на:
/* __unnamed is magic because modifying it gives UB. */ static char __unnamed[] = "abc"; char *c = __unnamed;
обратите внимание на неявное приведение от
char[]
tochar *
, что всегда законно.тогда, если вы измените
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 " автоматически выполняются для вас.