¿Cuál es la diferencia entre char s [] y char * s?

506

En C, se puede usar un literal de cadena en una declaración como esta:

char s[] = "hello";

o así:

char *s = "hello";

Entonces cuál es la diferencia? Quiero saber qué sucede realmente en términos de duración de almacenamiento, tanto en tiempo de compilación como de ejecución.

StoryTeller - Unslander Monica
fuente
8
char * s = "hola", aquí s puede apuntar cualquier otra cadena en tiempo de ejecución. Quiero decir que no es un puntero constante, puede asignar otro valor en tiempo de ejecución p = "Nishant", mientras que s [] aquí s es un puntero constante. ..no se puede reasignar otra cadena pero podemos asignar otro valor de carácter en s [index].
Nishant Kumar

Respuestas:

541

La diferencia aquí es que

char *s = "Hello world";

colocará "Hello world"en las partes de solo lectura de la memoria , y haciendos un puntero a eso, cualquier operación de escritura en esta memoria es ilegal.

Mientras se hace:

char s[] = "Hello world";

coloca la cadena literal en la memoria de solo lectura y copia la cadena en la memoria recién asignada en la pila. Haciendo así

s[0] = 'J';

legal.

Rickard
fuente
22
La cadena literal "Hello world"está en "partes de solo lectura de la memoria" en ambos ejemplos. El ejemplo con la matriz apunta allí, el ejemplo con la matriz copia los caracteres a los elementos de la matriz.
pmg
28
p.m. ellos.
caf
10
El ejemplo de matriz de caracteres no coloca necesariamente la cadena en la pila; si aparece a nivel de archivo, probablemente estará en algún tipo de segmento de datos inicializado.
caf
99
Me gustaría señalar que char s = "xx" no tiene que estar en la memoria de solo lectura (algunas implementaciones no tienen MMU, por ejemplo). El borrador n1362 c1x simplemente establece que la modificación de dicha matriz provoca un comportamiento indefinido. Pero +1 de todos modos, ya que confiar en ese comportamiento es algo tonto.
paxdiablo
3
Obtengo una compilación limpia en un archivo que contiene solo char msg[] = "hello, world!"; la cadena que termina en la sección de datos inicializados. Cuando se declara char * constque termina en la sección de datos de solo lectura. gcc-4.5.3
gcbenison
152

En primer lugar, en argumentos de función, son exactamente equivalentes:

void foo(char *x);
void foo(char x[]); // exactly the same in all respects

En otros contextos, char *asigna un puntero, mientras que char []asigna una matriz. ¿Dónde va la cuerda en el primer caso, preguntas? El compilador asigna secretamente una matriz anónima estática para contener el literal de cadena. Entonces:

char *x = "Foo";
// is approximately equivalent to:
static const char __secret_anonymous_array[] = "Foo";
char *x = (char *) __secret_anonymous_array;

Tenga en cuenta que nunca debe intentar modificar el contenido de esta matriz anónima a través de este puntero; los efectos son indefinidos (a menudo significan un bloqueo):

x[1] = 'O'; // BAD. DON'T DO THIS.

El uso de la sintaxis de la matriz lo asigna directamente a la nueva memoria. Por lo tanto, la modificación es segura:

char x[] = "Foo";
x[1] = 'O'; // No problem.

Sin embargo, la matriz solo vive mientras su alcance de contagio, por lo que si hace esto en una función, no devuelva ni pierda un puntero a esta matriz; haga una copia en su lugar con strdup()o similar. Si la matriz se asigna en alcance global, por supuesto, no hay problema.

bdonlan
fuente
72

Esta declaración:

char s[] = "hello";

Crea un objeto: una charmatriz de tamaño 6, llamada s, inicializada con los valores 'h', 'e', 'l', 'l', 'o', '\0'. La ubicación de esta matriz en la memoria y el tiempo de vida depende de dónde aparezca la declaración. Si la declaración está dentro de una función, vivirá hasta el final del bloque en el que se declara, y casi con certeza se asignará en la pila; si está fuera de una función, probablemente se almacenará dentro de un "segmento de datos inicializado" que se carga desde el archivo ejecutable en la memoria grabable cuando se ejecuta el programa.

Por otro lado, esta declaración:

char *s ="hello";

Crea dos objetos:

  • una matriz de solo lectura de 6 chars que contiene los valores 'h', 'e', 'l', 'l', 'o', '\0', que no tiene nombre y tiene una duración de almacenamiento estático (lo que significa que dura toda la vida del programa); y
  • una variable de tipo puntero a char, llamada s, que se inicializa con la ubicación del primer carácter en esa matriz de solo lectura sin nombre.

La matriz de solo lectura sin nombre generalmente se encuentra en el segmento de "texto" del programa, lo que significa que se carga desde el disco en la memoria de solo lectura, junto con el código mismo. La ubicación de la svariable del puntero en la memoria depende de dónde aparece la declaración (como en el primer ejemplo).

coste y flete
fuente
1
En ambas declaraciones para "hola", ¿la memoria se asigna en tiempo de respuesta? Y otra cosa char * p = "hola" aquí "hola" se almacena en el segmento de texto como usted indicó en su respuesta ... y ¿qué pasa con char s [] = "hola" también se almacenará primero en la parte del segmento de texto y durante el tiempo de ejecución se copiará en la pila como Rickard ha indicado en su respuesta. por favor aclare este punto.
Nishant Kumar
2
@Nishant: en el char s[] = "hello"caso, "hello"es solo un inicializador que le dice al compilador cómo se debe inicializar la matriz. Puede o no dar como resultado una cadena correspondiente en el segmento de texto; por ejemplo, si stiene una duración de almacenamiento estático, es probable que la única instancia de "hello"esté en el segmento de datos inicializado: el objeto en ssí. Incluso si stiene una duración de almacenamiento automática, puede inicializarse mediante una secuencia de almacenes literales en lugar de una copia (p. Ej. movl $1819043176, -6(%ebp); movw $111, -2(%ebp)).
caf
Más precisamente, GCC 4.8 lo coloca, en el .rodatacual el script del enlazador luego se descarga en el mismo segmento que .text. Mira mi respuesta .
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
@caf En la primera respuesta de Rickard, está escrito que char s[] = "Hello world";coloca la cadena literal en la memoria de solo lectura y copia la cadena a la memoria recién asignada en la pila. Sin embargo, su respuesta sólo se habla de la opción de venta literal de cadena en la memoria de sólo lectura y se salta la segunda parte de la frase que dice: copies the string to newly allocated memory on the stack. Entonces, ¿su respuesta es incompleta por no especificar la segunda parte?
KPMG
1
@AjaySinghNegi: Como dije en otros comentarios (a esta respuesta y a la respuesta de Rickard), la cadena char s[] = "Hellow world";es solo un inicializador y no se almacena necesariamente como una copia de solo lectura separada. Si stiene una duración de almacenamiento estático, es probable que la única copia de la cadena esté en un segmento de lectura-escritura en la ubicación de s, e incluso si no, el compilador puede optar por inicializar la matriz con instrucciones de carga inmediata o similar en lugar de copiar de una cadena de solo lectura. El punto es que en este caso, la cadena inicializadora en sí misma no tiene presencia en tiempo de ejecución.
caf
60

Dadas las declaraciones

char *s0 = "hello world";
char s1[] = "hello world";

suponga el siguiente mapa de memoria hipotético:

                    0x01 0x02 0x03 0x04
        0x00008000: 'h' 'e' 'l' 'l'
        0x00008004: 'o' '' 'w' 'o'
        0x00008008: 'r' 'l' 'd' 0x00
        ...
s0: 0x00010000: 0x00 0x00 0x80 0x00
s1: 0x00010004: 'h' 'e' 'l' 'l'
        0x00010008: 'o' '' 'w' 'o'
        0x0001000C: 'r' 'l' 'd' 0x00

El literal de cadena "hello world"es una matriz de 12 elementos char( const charen C ++) con una duración de almacenamiento estático, lo que significa que la memoria para él se asigna cuando se inicia el programa y permanece asignada hasta que finaliza el programa. Intentar modificar el contenido de un literal de cadena invoca un comportamiento indefinido.

La línea

char *s0 = "hello world";

se define s0como un puntero charcon una duración de almacenamiento automático (lo que significa que la variable s0solo existe para el alcance en el que se declara) y le copia la dirección del literal de cadena ( 0x00008000en este ejemplo). Tenga en cuenta que ya s0apunta a un literal de cadena, no debe ser utilizado como un argumento a cualquier función que se trate de modificarlo (por ejemplo, strtok(), strcat(), strcpy(), etc.).

La línea

char s1[] = "hello world";

se define s1como una matriz de 12 elementos de char(la longitud se toma del literal de cadena) con duración de almacenamiento automático y copia el contenido del literal a la matriz. Como puede ver en el mapa de memoria, tenemos dos copias de la cadena "hello world"; la diferencia es que puedes modificar la cadena contenida en s1.

s0y s1son intercambiables en la mayoría de los contextos; Aquí están las excepciones:

sizeof s0 == sizeof (char*)
sizeof s1 == 12

type of &s0 == char **
type of &s1 == char (*)[12] // pointer to a 12-element array of char

Puede reasignar la variable s0para que apunte a un literal de cadena diferente u otra variable. No puede reasignar la variable s1para que apunte a una matriz diferente.

John Bode
fuente
2
¡Creo que el mapa de memoria hipotético lo hace fácil de entender!
midnightBlue
32

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 una cadena de caracteres literal, opcionalmente encerrada 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[]a char *, 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 para inicializar una matriz de duración y longitud de almacenamiento estático, lo suficiente como 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" sy tcuyos 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 "matriz de 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.

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 escritura. Esto se puede observar con:

readelf -l a.out

que contiene:

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

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).

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
15
char s[] = "hello";

declara sser una matriz charcuyo tiempo es suficiente para contener el inicializador (5 + 1 chars) e inicializa la matriz copiando los miembros de la cadena literal literal en la matriz.

char *s = "hello";

declara sser un puntero a uno o más (en este caso más) charsy lo apunta directamente a una ubicación fija (solo lectura) que contiene el literal "hello".

CB Bailey
fuente
1
¿Qué método es preferible usar en las funciones si s no se cambiará, f (const char s []) o f (const char * s)?
psihodelia
1
@psihodelia: en una declaración de función no hay diferencia. En ambos casos ses un puntero a const char.
CB Bailey
4
char s[] = "Hello world";

Aquí shay una serie de caracteres que se pueden sobrescribir si lo deseamos.

char *s = "hello";

Se utiliza un literal de cadena para crear estos bloques de caracteres en algún lugar de la memoria al que sapunta este puntero . Aquí podemos reasignar el objeto al que apunta cambiando eso, pero siempre que apunte a una cadena literal, el bloque de caracteres al que apunta no se puede cambiar.

Sailaja
fuente
@bo Persson ¿Por qué el bloque de caracteres no se puede cambiar en el segundo caso?
Pankaj Mahato
3

Además, considere que, para fines de solo lectura, el uso de ambos es idéntico, puede acceder a un carácter indexando con []o con *(<var> + <index>) formato:

printf("%c", x[1]);     //Prints r

Y:

printf("%c", *(x + 1)); //Prints r

Obviamente, si intentas hacer

*(x + 1) = 'a';

Probablemente obtendrá un error de segmentación, ya que está intentando acceder a la memoria de solo lectura.

Nick Louloudakis
fuente
Esto de ninguna manera es diferente de x[1] = 'a';lo que segfault también (dependiendo de la plataforma, por supuesto).
glglgl
3

Solo para agregar: también obtienes diferentes valores para sus tamaños.

printf("sizeof s[] = %zu\n", sizeof(s));  //6
printf("sizeof *s  = %zu\n", sizeof(s));  //4 or 8

Como se mencionó anteriormente, para una matriz '\0'se asignará como el elemento final.

Muzab
fuente
2
char *str = "Hello";

Lo anterior establece que str apunte al valor literal "Hola", que está codificado en la imagen binaria del programa, que está marcado como de solo lectura en la memoria, significa que cualquier cambio en este literal de cadena es ilegal y arrojaría fallas de segmentación.

char str[] = "Hello";

copia la cadena a la memoria recién asignada en la pila. Por lo tanto, cualquier cambio en él está permitido y es legal.

means str[0] = 'M';

cambiará el str a "Mello".

Para obtener más detalles, consulte la pregunta similar:

¿Por qué aparece un error de segmentación al escribir en una cadena inicializada con "char * s" pero no "char s []"?

Mohit
fuente
0

En el caso de:

char *x = "fred";

x es un valor l , se le puede asignar. Pero en el caso de:

char x[] = "fred";

x no es un valor l, es un valor r, no se le puede asignar.

Lee-Man
fuente
3
Técnicamente, xes un valor no modificable. Sin embargo, en casi todos los contextos, evaluará a un puntero a su primer elemento, y eso valor es un valor.
caf
0
char *s1 = "Hello world"; // Points to fixed character string which is not allowed to modify
char s2[] = "Hello world"; // As good as fixed array of characters in string so allowed to modify

// s1[0] = 'J'; // Illegal
s2[0] = 'J'; // Legal
Atul
fuente
-1

A la luz de los comentarios aquí, debería ser obvio que: char * s = "hola"; Es una mala idea, y debe usarse en un ámbito muy limitado.

Esta podría ser una buena oportunidad para señalar que la "corrección constante" es una "cosa buena". Siempre que pueda, use la palabra clave "const" para proteger su código, de las personas que llaman o los programadores "relajados", que generalmente son más "relajados" cuando entran en juego los punteros.

Suficiente melodrama, esto es lo que uno puede lograr al adornar punteros con "const". (Nota: hay que leer las declaraciones de puntero de derecha a izquierda). Estas son las 3 formas diferentes de protegerse cuando se juega con punteros:

const DBJ* p means "p points to a DBJ that is const" 

- es decir, el objeto DBJ no se puede cambiar a través de p.

DBJ* const p means "p is a const pointer to a DBJ" 

- es decir, puede cambiar el objeto DBJ a través de p, pero no puede cambiar el puntero p mismo.

const DBJ* const p means "p is a const pointer to a const DBJ" 

- es decir, no puede cambiar el puntero p, ni puede cambiar el objeto DBJ a través de p.

Los errores relacionados con los intentos de mutaciones const-ant se detectan en tiempo de compilación. No hay tiempo de ejecución ni penalización de velocidad para const.

(Por supuesto, ¿está utilizando el compilador de C ++?)

--DBJ


fuente
Todo esto es correcto, pero no tiene nada que ver con la pregunta. Y en cuanto a su suposición sobre un compilador de C ++, la pregunta está etiquetada como C, no como C ++.
Fabio dice reinstalar a Mónica el
No hay nada malo en char * s = "const string";
Paul Smith