Qual é a diferença entre char array e char pointer em C?

216

Estou tentando entender os ponteiros em C, mas atualmente estou confuso com o seguinte:

  • char *p = "hello"

    Este é um ponteiro de caractere apontando para a matriz de caracteres, começando em h .

  • char p[] = "hello"

    Essa é uma matriz que armazena oi .

Qual é a diferença quando passo essas duas variáveis ​​para essa função?

void printSomething(char *p)
{
    printf("p: %s",p);
}
diesel
fonte
5
Isso não seria válido: char p[3] = "hello";a cadeia de inicialização é muito longa para o tamanho da matriz que você declara. Erro de digitação?
Cody Gray
16
Ou apenas char p[]="hello";seria suficiente!
deepdive
possível repetição de C: diferenças entre ponteiro carvão animal e matriz
sashoalm
1
possível duplicata de Qual é a diferença entre char s [] e char * s em C? É verdade que isso também pergunta especificamente sobre o parâmetro de função, mas isso não é charespecífico.
Ciro Santilli escreveu
1
você precisa entender que eles são fundamentalmente diferentes. o único ponto em comum nisso é que a base do arry p [] é um ponteiro const que permite acessar o array p [] através de um ponteiro. O próprio p [] mantém a memória de uma string, enquanto * p apenas aponta para o endereço do primeiro elemento de apenas ONE CHAR (ou seja, aponta para a base da string já alocada). Para ilustrar melhor isso, considere abaixo: char * cPtr = {'h', 'e', ​​'l', 'l', 'o', '\ 0'}; ==> isso é um erro, pois cPtr é um ponteiro para apenas um caractere char cBuff [] = {'h', 'e', ​​'l', 'l', 'o', '\ 0'}; ==> Esta é Ok, bcos si cBuff é uma matriz de char
Ilavarasan

Respostas:

222

char*e char[] são tipos diferentes , mas não é imediatamente aparente em todos os casos. Isso ocorre porque as matrizes se decompõem em ponteiros , o que significa que, se uma expressão de tipo char[]for fornecida onde char*é esperado um tipo , o compilador converte automaticamente a matriz em um ponteiro para o seu primeiro elemento.

Seu exemplo de função printSomethingespera um ponteiro, portanto, se você tentar passar um array para ele assim:

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

O compilador finge que você escreveu isso:

char s[10] = "hello";
printSomething(&s[0]);
Jon
fonte
Algo mudou de 2012 para agora. Para uma matriz de caracteres "s" impressões matriz inteira .. ou seja, "Olá"
Bhanu Tez
@BhanuTez Não, como os dados são armazenados e o que é feito com os dados são preocupações separadas. Este exemplo imprime a cadeia inteira porque é assim que printflida com a %scadeia de formato: inicie no endereço fornecido e continue até encontrar o terminador nulo. Se você quiser imprimir apenas um caractere, poderá usar a %cstring de formato, por exemplo.
iX3 17/06/19
Só queria perguntar se char *p = "abc";o caractere NULL \0é automaticamente anexado, como no caso do array char []?
KPMG
Por que posso definir, char *name; name="123";mas posso fazer o mesmo com o inttipo? E depois de usar %cpara imprimir name, a saída é uma sequência ilegível :?
TomSawyer 23/04
83

Vamos ver:

#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 * e foo [] são tipos diferentes e são tratados de forma diferente pelo compilador (ponteiro = endereço + representação do tipo do ponteiro, matriz = ponteiro + comprimento opcional da matriz, se conhecido, por exemplo, se a matriz estiver alocada estaticamente ), os detalhes podem ser encontrados no padrão. E no nível do tempo de execução, não há diferença entre eles (em assembler, bem, quase, veja abaixo).

Além disso, há uma pergunta relacionada na C FAQ :

P : Qual é a diferença entre essas inicializações?

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

Meu programa falha se eu tentar atribuir um novo valor a p [i].

R : Um literal de cadeia (o termo formal para uma cadeia de aspas duplas na fonte C) pode ser usado de duas maneiras ligeiramente diferentes:

  1. Como inicializador de uma matriz de char, como na declaração de char a [], especifica os valores iniciais dos caracteres nessa matriz (e, se necessário, seu tamanho).
  2. Em qualquer outro lugar, ele se transforma em uma matriz estática e sem nome de caracteres, e essa matriz sem nome pode ser armazenada na memória somente leitura e, portanto, não pode ser necessariamente modificada. Em um contexto de expressão, a matriz é convertida imediatamente em um ponteiro, como de costume (consulte a seção 6), de modo que a segunda declaração inicializa p para apontar para o primeiro elemento da matriz sem nome.

Alguns compiladores têm uma opção que controla se os literais de string são graváveis ​​ou não (para compilar código antigo), e alguns podem ter opções para fazer com que os literais de string sejam tratados formalmente como matrizes de const char (para melhor captura de erros).

Veja também as perguntas 1.31, 6.1, 6.2, 6.8 e 11.8b.

Referências: K & R2 Sec. 5,5 p. 104

ISO Sec. 6.1.4, Sec. 6.5.7

Fundamentação Sec. 3.1.4

H&S Sec. 2.7.4 pp. 31-2

JJJ
fonte
Em sizeof (q), por que q não se decompõe em um ponteiro, como @Jon menciona em sua resposta?
Garyp
@garyp q não decai em um ponteiro porque sizeof é um operador, não uma função (mesmo que sizeof fosse uma função, q decairia apenas se a função estivesse esperando um ponteiro de char).
GiriB 14/08
graças, mas printf ( "% u \ n" em vez de printf ( "% zu \ n", eu acho que você deve remover z.
Zakaria
33

Qual é a diferença entre char array vs char pointer em C?

C99 N1256 draft

Existem dois usos diferentes dos literais da cadeia de caracteres:

  1. Inicialize char[]:

    char c[] = "abc";      

    Isso é "mais mágico" e descrito em 6.7.8 / 14 "Inicialização":

    Uma matriz do tipo de caractere pode ser inicializada por uma cadeia de caracteres literal, opcionalmente entre chaves. Caracteres sucessivos da literal da cadeia de caracteres (incluindo o caractere nulo final, se houver espaço ou se a matriz for de tamanho desconhecido) inicializam os elementos da matriz.

    Portanto, este é apenas um atalho para:

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

    Como qualquer outra matriz regular, cpode ser modificado.

  2. Em qualquer outro lugar: gera um:

    Então, quando você escreve:

    char *c = "abc";

    Isso é semelhante a:

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

    Observe a conversão implícita de char[]para char *, que é sempre legal.

    Então, se você modificar c[0], também modifique __unnamed, que é UB.

    Isso está documentado em 6.4.5 "String literals":

    5 Na fase de conversão 7, um byte ou código de valor zero é anexado a cada sequência de caracteres multibyte que resulta de uma string literal ou literal. A sequência de caracteres multibyte é então usada para inicializar uma matriz de duração e comprimento estáticos de armazenamento apenas o suficiente para conter a sequência. Para literais da cadeia de caracteres, os elementos da matriz têm o tipo char e são inicializados com os bytes individuais da sequência de caracteres multibyte [...]

    6 Não é especificado se essas matrizes são distintas, desde que seus elementos tenham os valores apropriados. Se o programa tentar modificar essa matriz, o comportamento será indefinido.

6.7.8 / 32 "Inicialização" dá um exemplo direto:

EXEMPLO 8: A declaração

char s[] = "abc", t[3] = "abc";

define objetos de matriz de caracteres "simples" se tcujos elementos são inicializados com literais de cadeia de caracteres.

Esta declaração é idêntica à

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

O conteúdo das matrizes é modificável. Por outro lado, a declaração

char *p = "abc";

define pcom o tipo "ponteiro para char" e o inicializa para apontar para um objeto com o tipo "array of char" com comprimento 4 cujos elementos são inicializados com uma literal de cadeia de caracteres. Se for feita uma tentativa pde modificar o conteúdo da matriz, o comportamento será indefinido.

Implementação do GCC 4.8 x86-64 ELF

Programa:

#include <stdio.h>

int main(void) {
    char *s = "abc";
    printf("%s\n", s);
    return 0;
}

Compilar e descompilar:

gcc -ggdb -std=c99 -c main.c
objdump -Sr main.o

A saída contém:

 char *s = "abc";
8:  48 c7 45 f8 00 00 00    movq   $0x0,-0x8(%rbp)
f:  00 
        c: R_X86_64_32S .rodata

Conclusão: o GCC o armazena char*em .rodataseção, não em .text.

Se fizermos o mesmo para char[]:

 char s[] = "abc";

nós obtemos:

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

para que seja armazenado na pilha (em relação a %rbp).

Observe, no entanto, que o script do vinculador padrão coloca .rodatae .textno mesmo segmento, que tem execução, mas não possui permissão de gravação. Isso pode ser observado com:

readelf -l a.out

que contém:

 Section to Segment mapping:
  Segment Sections...
   02     .text .rodata
Ciro Santilli adicionou uma nova foto
fonte
2
@ leszek.hanusz Comportamento indefinido stackoverflow.com/questions/2766731/… Google "Linguagem C UB" ;-)
Ciro Santilli #
9

Você não tem permissão para alterar o conteúdo de uma constante de string, que é o que o primeiro paponta. O segundo pé uma matriz inicializada com uma constante de sequência e você pode alterar seu conteúdo.

potrzebie
fonte
6

Em casos como esse, o efeito é o mesmo: você acaba passando o endereço do primeiro caractere em uma sequência de caracteres.

Obviamente, as declarações não são as mesmas.

A seguir, a memória é reservada para uma seqüência de caracteres e também para um ponteiro de caractere e, em seguida, inicializa o ponteiro para apontar para o primeiro caractere na seqüência de caracteres.

char *p = "hello";

Enquanto o seguinte separa a memória apenas para a string. Portanto, ele pode realmente usar menos memória.

char p[10] = "hello";
Jonathan Wood
fonte
codeplusplus.blogspot.com/2007/09/… "No entanto, a inicialização da variável exige uma enorme penalidade de desempenho e espaço para a matriz"
leef
@ leef: Eu acho que depende de onde a variável está localizada. Se estiver na memória estática, acho que é possível que a matriz e os dados sejam armazenados na imagem EXE e não exijam nenhuma inicialização. Caso contrário, sim, certamente pode ser mais lento se os dados têm que ser alocados e, em seguida, os dados estática tem de ser copiado no.
Jonathan Madeira
3

Tanto quanto me lembro, uma matriz é na verdade um grupo de indicadores. Por exemplo

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

é uma afirmação verdadeira

CosminO
fonte
2
Eu descreveria uma matriz como sendo um ponteiro para o endereço de um bloco de memória. Por isso, o *(arr + 1)leva ao segundo membro de arr. Se *(arr)aponta para um endereço de memória de 32 bits, por exemplo bfbcdf5e, *(arr + 1)aponta para bfbcdf60(o segundo byte). Portanto, por que sair do escopo de uma matriz levará a resultados estranhos se o sistema operacional não fizer um defeito. Se int a = 24;estiver no endereço bfbcdf62, o acesso arr[2]poderá retornar 24, assumindo que um segfault não ocorra primeiro.
Braden Best
3

No APUE , Seção 5.14:

char    good_template[] = "/tmp/dirXXXXXX"; /* right way */
char    *bad_template = "/tmp/dirXXXXXX";   /* wrong way*/

... Para o primeiro modelo, o nome é alocado na pilha, porque usamos uma variável de matriz. Para o segundo nome, no entanto, usamos um ponteiro. Nesse caso, apenas a memória do ponteiro em si reside na pilha; o compilador organiza a cadeia de caracteres a ser armazenada no segmento somente leitura do executável. Quando a mkstempfunção tenta modificar a cadeia, ocorre uma falha de segmentação.

O texto citado corresponde à explicação de @Ciro Santilli.

Rick
fonte
1

char p[3] = "hello"? deve ser char p[6] = "hello"lembrar que há um '\ 0' char no final de uma "string" em C.

de qualquer maneira, a matriz em C é apenas um ponteiro para o primeiro objeto de um objeto de ajuste na memória. os únicos s diferentes estão na semântica. enquanto você pode alterar o valor de um ponteiro para apontar para um local diferente na memória, uma matriz, depois de criada, sempre apontará para o mesmo local.
Além disso, ao usar o array, o "novo" e o "excluir" são feitos automaticamente para você.

Roee Gavirel
fonte