initialize array c string char constants

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



string array c (12)

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 del almacenamiento, tanto en tiempo de compilación como en tiempo de ejecución.


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

Esta podría ser una buena oportunidad para señalar que la "corrección de constantes" es una "cosa buena". Siempre que pueda y donde sea que pueda, use la palabra clave "const" para proteger su código, de los llamadores o programadores "relajados", que suelen estar 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.

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

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

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

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

--DBJ


Como adición, tenga en cuenta que, al igual que para fines de solo lectura, el uso de ambos es idéntico, puede acceder a un char indexando ya sea con el formato [] o *(<var> + <index>) :

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.


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

La cadena literal "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 ella se asigna cuando el programa se inicia y permanece asignada hasta que el programa termina. El intento de modificar el contenido de una cadena literal 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 de la cadena literal ( 0x00008000 en este ejemplo). Tenga en cuenta que dado que s0 apunta a una cadena literal, no debe usarse como argumento para ninguna función que intente modificarla (por ejemplo, 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 del literal de la cadena) con duración de almacenamiento automático y copia el contenido del literal a la matriz. Como se 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 puede reasignar la variable s1 para que apunte a una matriz diferente.


En el caso de:

char *x = "fred";

x es un lvalue - puede ser asignado a Pero en el caso de:

char x[] = "fred";

x no es un lvalor, es un rvalor, no se le puede asignar.


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 una matriz. ¿Dónde va la cadena en el caso anterior, te preguntarás? El compilador asigna secretamente una matriz anónima estática para mantener la cadena literal. Asi que:

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 no están definidos (lo que a menudo significa un fallo):

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

El uso de la sintaxis de matriz lo asigna directamente a la nueva memoria. Así la modificación es segura:

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

Sin embargo, la matriz solo dura el tiempo que se mantiene, por lo que si hace esto 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 está asignada en el ámbito global, por supuesto, no hay problema.


Esta declaración:

char s[] = "hello";

Crea un objeto: una matriz de caracteres 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 su duración 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 es casi seguro que se asignará en la pila; si está fuera de una función, probablemente se almacenará dentro de un "segmento de datos inicializados" que se carga desde el archivo ejecutable a 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 caracteres que contiene los valores ''h'', ''e'', ''l'', ''l'', ''o'', ''/0'' , que no tiene nombre y tiene una duración de almacenamiento estática (lo que significa que vidas para toda la vida del programa); y
  • una variable de tipo puntero a carácter, llamada s , que se inicializa con la ubicación del primer carácter en esa matriz sin nombre, de solo lectura.

La matriz de solo lectura sin nombre normalmente 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 propio código. La ubicación de la variable de puntero s en la memoria depende de dónde aparece la declaración (como en el primer ejemplo).


La diferencia aquí es que

char *s = "Hello world";

colocará "Hello world" en las partes de solo lectura de la memoria , y haciendo 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 en la memoria recién asignada en la pila. Así haciendo

s[0] = ''J'';

legal.


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.


C99 N1256 borrador

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

  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 encerrado entre llaves. Los caracteres sucesivos del literal de la 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.

    Así que esto es sólo un atajo para:

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

    Como cualquier otra matriz regular, c puede ser modificado.

  2. En todas partes: genera un:

    Así que 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 está documentado 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 un literal de cadena o literales. La secuencia de caracteres multibyte luego se usa para inicializar una matriz de duración y longitud de almacenamiento estático suficiente para contener la secuencia. Para los literales de cadena de caracteres, los elementos de la matriz tienen 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 adecuados. 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 los 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 p con 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 p para modificar el contenido de la matriz, el comportamiento no está definido.

GCC 4.8 x86-64 Implementación 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 almacena char* it en la sección .rodata , 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 (relativa a %rbp ).

Sin embargo, .text en cuenta que la secuencia de comandos de vinculador predeterminada coloca .rodata y .text en el mismo segmento, que tiene permisos de escritura pero no de ejecución. Esto se puede observar con:

readelf -l a.out

que contiene:

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


char *str = "Hello";

Lo anterior establece str para que 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 eso generaría fallas de segmentación.

char str[] = "Hello";

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

means str[0] = ''M'';

cambiará el str a "mello".

Para más detalles, pase por la pregunta similar:

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


char s[] = "Hello world";

Aquí, s es un conjunto 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 a la que apunta este puntero s . 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.


char s[] = "hello";

declara que s es una matriz de caracteres que es lo suficientemente larga como para contener el inicializador (5 + 1 caracteres) y que inicializa la matriz copiando los miembros de la cadena literal dada en la matriz.

char *s = "hello";

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