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


En C, uno 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.

Author: StoryTeller, 2009-11-10

12 answers

La diferencia aquí es que

char *s = "Hello world";

Colocará "Hello world"en las partes de solo lectura de la memoria, y haciendo s un puntero a eso hace que cualquier operación de escritura en esta memoria sea ilegal.

Mientras se hace:

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. Así haciendo

s[0] = 'J';

Legal.

 487
Author: Rickard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-11 14:28:58

En primer lugar, en los 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 un array. ¿Dónde va la cuerda en el primer caso, te 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 crash):

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

Usando la sintaxis del array 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 ámbito de contaning, por lo que si lo hace en una función, no devuelva ni filtre un puntero a esta matriz, haga una copia en su lugar con strdup() o similar. Si la matriz se asigna en el ámbito global, por supuesto, no hay problema.

 136
Author: bdonlan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-02 17:52:30

Esta declaración:

char s[] = "hello";

Crea un objeto - una matriz char 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 que dura, 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 seguramente se asignará a la pila; si está fuera de una función, probablemente se almacenará dentro de una función "segmento de datos inicializado" que se carga desde el archivo ejecutable en la memoria de escritura cuando se ejecuta el programa.

Por otra parte, esta declaración:

char *s ="hello";

Crea dos objetos:

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

La matriz de solo lectura sin nombre se encuentra típicamente en el segmento "texto" del programa, lo que significa que se carga desde el disco en la memoria de solo lectura, junto con el código en sí. La ubicación de la variable puntero s en la memoria depende de dónde aparezca la declaración (al igual que en el primer ejemplo).

 62
Author: caf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-11-09 22:50:25

Dadas las declaraciones

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

Supongamos 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 de char (const char en C++) con duración de almacenamiento estático, lo que significa que la memoria para ello se asigna cuando el programa se inicia y permanece asignada hasta que el programa termina. Intentar modificar el contenido de un literal de cadena invoca un comportamiento indefinido.

La línea

char *s0 = "hello world";

Define s0 como un puntero a char con duración de almacenamiento automático (lo que significa que la variable s0 solo existe para el ámbito en el que se declara) y copia la dirección del literal de cadena (0x00008000 en este ejemplo). Tenga en cuenta que dado que s0 apunta a un literal de cadena, no debe usarse como argumento para ninguna función que intente modificarlo (p. ej., strtok(), strcat(), strcpy(), etc.).

La línea

char s1[] = "hello world";

Define s1 como una matriz de 12 elementos de char (la longitud se toma de la cadena literal) 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 puede modificar la cadena contenida en s1.

s0 y s1 son 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 s0 para que apunte a un literal de cadena diferente o a otra variable. No se puede reasignar la variable s1 para apuntar a una matriz diferente.

 53
Author: John Bode,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-26 15:22:09

C99 N1256 draft

Hay dos usos completamente diferentes de los literales de matriz:

  1. Inicializar char[]:

    char c[] = "abc";      
    

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

    Una matriz de tipo de carácter puede ser inicializada por un literal de cadena de caracteres, opcionalmente encerrados en llaves. Caracteres sucesivos del literal de la cadena de caracteres (incluyendo terminar el carácter nulo si hay espacio o si la matriz es de tamaño desconocido) inicializar el elementos de la matriz.

    Así que esto es solo un atajo para:

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

    Como cualquier otro array regular, c puede ser modificado.

  2. En todas partes: genera un:{[34]]}

    Así que cuando escribe:

    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 el reparto implícito de char[] a char *, que siempre es legal.

    Entonces si modificas c[0], también modificas __unnamed, que es UB.

    Esto está documentado en 6.4.5 "literales de Cadena":

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

    6 No se especifica si estos arrays son distintos siempre que sus elementos tengan la valores apropiados. Si el programa intenta modificar dicha matriz, el comportamiento es indefinido.

6.7.8/32 "La inicialización" da un ejemplo directo:

EJEMPLO 8: La declaración

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

Define objetos de matriz de caracteres "simples" s y t 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' };

El contenido de los arrays es modificable. Por otra parte, la declaración

char *p = "abc";

Define p con tipo "pointer to char "y lo inicializa para apuntar a un objeto con el tipo" array of char " con longitud 4 cuyos elementos se inicializan con un literal de cadena de caracteres. Si se intenta usar p para modificar el contenido del array, el comportamiento es indefinido.

GCC 4.8 x86-64 Implementación del ELF

Programa:

#include <stdio.h>

int main() {
    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 almacena char* en .rodata sección, no{[33] {} en[26]}.

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 el script enlazador predeterminado pone .rodata y .text en 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 
 27
Author: Ciro Santilli 新疆改造中心 六四事件 法轮功,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-13 07:22:55
char s[] = "hello";

Declara que s es una matriz de charque es lo suficientemente larga como para contener el inicializador (5 + 1 char s) e inicializa la matriz copiando los miembros del literal de cadena dado en la matriz.

char *s = "hello";

Declara que ses un puntero a uno o más (en este caso más) char s y lo apunta directamente a una ubicación fija (de solo lectura) que contiene el literal "hello".

 15
Author: CB Bailey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-05 14:12:10
char s[] = "Hello world";

Aquí, s es una matriz de caracteres, que se pueden sobrescribir si lo deseamos.

char *s = "hello";

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

 4
Author: Sailaja,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-27 15:10:58

Como adición, tenga en cuenta que, como para fines de solo lectura el uso de ambos es idéntico, puede acceder a un carácter indexando ya sea con [] o *(<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á tratando de acceder a la memoria de solo lectura.

 3
Author: Nick Louloudakis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-30 10:22:03

Solo para agregar: también obtiene 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 un array '\0' se asignará como el elemento final.

 3
Author: Muzab,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-31 15:28:44
char *str = "Hello";

Lo anterior establece str para apuntar al valor literal "Hello" 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 que lanzarí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 más detalles, por favor ir a través de la similar pregunta:

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

 2
Author: Mohit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:36

En el caso de:

char *x = "fred";

X es un lvalue can al que se le puede asignar. Pero en el caso de:

char x[] = "fred";

X no es un lvalue, es un rvalue cannot no se le puede asignar.

 0
Author: Lee-Man,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-11-09 22:57:04

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

Esta podría ser una buena oportunidad para señalar que la "corrección de la constancia" es una "cosa buena". Cuando y donde sea que puedas, usa la palabra clave " const "para proteger tu código, de llamadas o programadores" relajados", que generalmente son más" relajados " cuando los punteros entran en juego.

Basta de melodrama, esto es lo que se puede lograr al adornar punteros con "const". (Nota: Uno tiene que leer las declaraciones de puntero de derecha a izquierda.) Estas son las 3 formas diferentes de protegerse al jugar 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 en sí.

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

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

Los errores relacionados con el intento las mutaciones constantes se detectan en tiempo de compilación. No hay espacio de tiempo de ejecución o penalización de velocidad para const.

(Suponiendo que está utilizando el compilador de C++, por supuesto ?)

DB DBJ

 0
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-11-09 23:27:55