¿Cuál es la diferencia entre char array y char pointer en C?

216

Estoy tratando de entender los punteros en C pero actualmente estoy confundido con lo siguiente:

  • char *p = "hello"

    Este es un puntero de caracteres que apunta a la matriz de caracteres, comenzando en h .

  • char p[] = "hello"

    Esta es una matriz que almacena hola .

¿Cuál es la diferencia cuando paso ambas variables a esta función?

void printSomething(char *p)
{
    printf("p: %s",p);
}
diesel
fuente
55
Esto no sería válido: char p[3] = "hello";la cadena del inicializador es demasiado larga para el tamaño de la matriz que declara. ¿Error de tipografía?
Cody Gray
16
¡O simplemente char p[]="hello";sería suficiente!
deepdive
1
posible duplicado de ¿Cuál es la diferencia entre char s [] y char * s en C? Es cierto que esto también pregunta específicamente sobre el parámetro de la función, pero eso no es charespecífico.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
necesitas entender que son fundamentalmente diferentes. La única característica común en esto es que la base del arry p [] es un puntero constante que permite acceder a la matriz p [] a través de un puntero. p [] en sí contiene memoria para una cadena, mientras que * p solo apunta a la dirección del primer elemento de solo ONE CHAR (es decir, apunta a la base de la cadena ya asignada). Para ilustrar mejor esto, considere a continuación: char * cPtr = {'h', 'e', ​​'l', 'l', 'o', '\ 0'}; ==> esto es un error, ya que cPtr es un puntero a solo un caracter char cBuff [] = {'h', 'e', ​​'l', 'l', 'o', '\ 0'}; ==> Esto está bien, bcos cBuff en sí es una matriz de caracteres
Ilavarasan

Respuestas:

222

char*y char[] son de diferentes tipos , pero no es inmediatamente aparente en todos los casos. Esto se debe a que las matrices decaen en punteros , lo que significa que si char[]se proporciona una expresión de tipo donde char*se espera una de tipo , el compilador convierte automáticamente la matriz en un puntero a su primer elemento.

Su función de ejemplo printSomethingespera un puntero, por lo que si intenta pasarle una matriz de esta manera:

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

El compilador finge que escribiste esto:

char s[10] = "hello";
printSomething(&s[0]);
Jon
fuente
¿Ha cambiado algo desde 2012 hasta ahora? Para un conjunto de caracteres "s" imprime todo el conjunto ... es decir, "hola"
Bhanu Tez
@BhanuTez No, cómo se almacenan los datos y qué se hace con los datos son preocupaciones separadas. Este ejemplo imprime la cadena completa porque así es como printfmaneja la %scadena de formato: comience en la dirección proporcionada y continúe hasta encontrar un terminador nulo. Si desea imprimir solo un carácter, puede utilizar la %ccadena de formato, por ejemplo.
iX3
¿Solo quería preguntar si char *p = "abc";el carácter NULL \0se agrega automáticamente como en el caso de la matriz char []?
KPMG
¿Por qué puedo configurar char *name; name="123";pero puedo hacer lo mismo con el inttipo? Y después de usar %cpara imprimir name, la salida es una cadena ilegible :?
TomSawyer
83

Veamos:

#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 * y foo [] son ​​tipos diferentes y el compilador los maneja de manera diferente (puntero = dirección + representación del tipo de puntero, matriz = puntero + longitud opcional de la matriz, si se conoce, por ejemplo, si la matriz está estáticamente asignada ), los detalles se pueden encontrar en el estándar. Y a nivel de tiempo de ejecución no hay diferencia entre ellos (en ensamblador, bueno, casi, ver más abajo).

Además, hay un relacionado pregunta en las preguntas frecuentes de C :

P : ¿Cuál es la diferencia entre estas inicializaciones?

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

Mi programa se bloquea si intento asignar un nuevo valor a p [i].

UNA : Un literal de cadena (el término formal para una cadena entre comillas dobles en fuente C) se puede usar de dos maneras ligeramente diferentes:

  1. Como inicializador de un conjunto de caracteres, como en la declaración de carácter a [], especifica los valores iniciales de los caracteres en ese conjunto (y, si es necesario, su tamaño).
  2. En cualquier otro lugar, se convierte en una matriz estática de caracteres sin nombre, y esta matriz sin nombre se puede almacenar en la memoria de solo lectura y, por lo tanto, no se puede modificar necesariamente. En un contexto de expresión, la matriz se convierte de inmediato en un puntero, como de costumbre (consulte la sección 6), por lo que la segunda declaración inicializa p para apuntar al primer elemento de la matriz sin nombre.

Algunos compiladores tienen un interruptor que controla si los literales de cadena se pueden escribir o no (para compilar código antiguo), y algunos pueden tener opciones para hacer que los literales de cadena se traten formalmente como matrices de caracteres constantes (para una mejor captura de errores).

Véanse también las preguntas 1.31, 6.1, 6.2, 6.8 y 11.8b.

Referencias: K & R2 Sec. 5.5 p. 104

Sec. ISO 6.1.4, Sec. 6.5.7

Justificación Sec. 3.1.4

Sec. H&S 2.7.4 págs. 31-2

JJJ
fuente
En sizeof (q), ¿por qué q no decae en un puntero, como @Jon menciona en su respuesta?
garyp
@garyp q no se descompone en un puntero porque sizeof es un operador, no una función (incluso si sizeof fuera una función, q decaería solo si la función esperaba un puntero char).
GiriB
gracias, pero printf ("% u \ n" en lugar de printf ("% zu \ n", creo que deberías eliminar z.
Zakaria
33

¿Cuál es la diferencia entre char array y char pointer en C?

C99 N1256 draft

Hay dos usos diferentes de los literales de cadena de caracteres:

  1. Inicializar char[]:

    char c[] = "abc";      

    Esto es "más mágico", y se describe en 6.7.8 / 14 "Inicialización":

    Una matriz de tipo de caracteres puede ser inicializada por un literal de cadena de caracteres, opcionalmente encerrado entre llaves. Los caracteres sucesivos del literal de cadena de caracteres (incluido el carácter nulo de terminación si hay espacio o si la matriz es de tamaño desconocido) inicializan los elementos de la matriz.

    Entonces esto es solo un atajo para:

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

    Al igual que cualquier otra matriz regular, cse puede modificar.

  2. En todas partes: genera un:

    Entonces cuando escribes:

    char *c = "abc";

    Esto es similar a:

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

    Tenga en cuenta la conversión implícita de char[]achar * , que siempre es legal.

    Luego, si modifica c[0], también modifica__unnamed , que es UB.

    Esto se documenta en 6.4.5 "Literales de cadena":

    5 En la fase de traducción 7, se agrega un byte o código de valor cero a cada secuencia de caracteres multibyte que resulta de una cadena literal o literales. La secuencia de caracteres multibyte se usa luego para inicializar una matriz de duración y longitud de almacenamiento estático solo suficiente para contener la secuencia. Para los literales de cadena de caracteres, los elementos de la matriz tienen el tipo char y se inicializan con los bytes individuales de la secuencia de caracteres multibyte [...]

    6 No se especifica si estas matrices son distintas siempre que sus elementos tengan los valores apropiados. Si el programa intenta modificar dicha matriz, el comportamiento es indefinido.

6.7.8 / 32 "Inicialización" da un ejemplo directo:

EJEMPLO 8: La declaración

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

define objetos de matriz de caracteres "simples" syt cuyos elementos se inicializan con literales de cadena de caracteres.

Esta declaración es idéntica a

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

Los contenidos de las matrices son modificables. Por otro lado, la declaración

char *p = "abc";

define pcon el tipo "puntero a char" y lo inicializa para apuntar a un objeto con el tipo "array of char" con longitud 4 cuyos elementos se inicializan con una cadena de caracteres literal. Si se intenta utilizar ppara modificar el contenido de la matriz, el comportamiento no está definido.

Implementación de GCC 4.8 x86-64 ELF

Programa:

#include <stdio.h>

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

Compilar y descompilar:

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

La salida contiene:

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

Conclusión: GCC lo almacena char*en .rodatasección, no en.text .

Si hacemos lo mismo para char[]:

 char s[] = "abc";

obtenemos:

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

por lo que se almacena en la pila (en relación con %rbp ).

Sin embargo , tenga en cuenta que la secuencia de comandos del vinculador predeterminado coloca .rodatay .texten el mismo segmento, que tiene permiso de ejecución pero no de escritura. Esto se puede observar con:

readelf -l a.out

que contiene:

 Section to Segment mapping:
  Segment Sections...
   02     .text .rodata
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
2
@ leszek.hanusz Comportamiento indefinido stackoverflow.com/questions/2766731/… Google "C language UB" ;-)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
9

No está permitido cambiar el contenido de una constante de cadena, que es a lo que papunta el primero . El segundo pes una matriz inicializada con una constante de cadena, y puede cambiar su contenido.

potrzebie
fuente
6

Para casos como este, el efecto es el mismo: terminas pasando la dirección del primer carácter en una cadena de caracteres.

Sin embargo, las declaraciones obviamente no son las mismas.

A continuación se reserva memoria para una cadena y también un puntero de caracteres, y luego se inicializa el puntero para que apunte al primer carácter de la cadena.

char *p = "hello";

Mientras que lo siguiente reserva memoria solo para la cadena. Por lo tanto, en realidad puede usar menos memoria.

char p[10] = "hello";
Jonathan Wood
fuente
codeplusplus.blogspot.com/2007/09/… "Sin embargo, la inicialización de la variable requiere un gran rendimiento y penalización de espacio para la matriz"
leef
@leef: Creo que eso depende de dónde se encuentre la variable. Si está en memoria estática, creo que es posible que la matriz y los datos se almacenen en la imagen EXE y no requieran ninguna inicialización. De lo contrario, sí, ciertamente puede ser más lento si los datos tienen que asignarse y luego los datos estáticos deben copiarse.
Jonathan Wood
3

Hasta donde puedo recordar, una matriz es en realidad un grupo de punteros. Por ejemplo

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

es una afirmación verdadera

CosminO
fuente
2
Describiría una matriz como un puntero a la dirección de un bloque de memoria. Por eso *(arr + 1)te lleva al segundo miembro de arr. Si *(arr)apunta a una dirección de memoria de 32 bits, por ejemplo bfbcdf5e, *(arr + 1)apunta a bfbcdf60(el segundo byte). Por lo tanto, por qué salir del alcance de una matriz conducirá a resultados extraños si el sistema operativo no falla por defecto. Si int a = 24;está en la dirección bfbcdf62, entonces el acceso arr[2]podría volver 24, suponiendo que no ocurra primero un segfault.
Braden Best
3

De APUE , Sección 5.14:

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

... Para la primera plantilla, el nombre se asigna en la pila, porque usamos una variable de matriz. Para el segundo nombre, sin embargo, usamos un puntero. En este caso, solo la memoria para el puntero reside en la pila; el compilador organiza que la cadena se almacene en el segmento de solo lectura del ejecutable. Cuando la mkstempfunción intenta modificar la cadena, se produce un error de segmentación.

El texto citado coincide con la explicación de @Ciro Santilli.

Almiar
fuente
1

char p[3] = "hello"? debe char p[6] = "hello"recordarse que hay un carácter '\ 0' al final de una "cadena" en C.

de todos modos, la matriz en C es solo un puntero al primer objeto de un objeto de ajuste en la memoria. Las únicas diferencias son en semántica. Si bien puede cambiar el valor de un puntero para apuntar a una ubicación diferente en la memoria, una matriz, una vez creada, siempre apuntará a la misma ubicación.
también cuando se usa la matriz, "nuevo" y "eliminar" se hacen automáticamente por usted.

Roee Gavirel
fuente