C: diferencias entre puntero char y array

Considerar:

char amessage[] = "now is the time"; char *pmessage = "now is the time"; 

Leí en The C Programming Language , 2nd Edition que las dos afirmaciones anteriores no hacen lo mismo.

Siempre pensé que una matriz es una forma conveniente de manipular punteros para almacenar algunos datos, pero claramente este no es el caso … ¿Cuáles son las diferencias “no triviales” entre matrices y punteros en C?

Es cierto, pero es una diferencia sutil. Esencialmente, el primero:

 char amessage[] = "now is the time"; 

Define una matriz cuyos miembros viven en el espacio de la stack del scope actual, mientras que:

 char *pmessage = "now is the time"; 

Define un puntero que vive en el espacio de la stack del scope actual, pero que hace referencia a la memoria en otro lugar (en este caso, “ahora es el momento” se almacena en otra parte de la memoria, comúnmente una tabla de cadenas).

Además, tenga en cuenta que debido a que los datos que pertenecen a la segunda definición (el puntero explícito) no se almacenan en el espacio de stack del scope actual, no se especifica exactamente dónde se almacenará y no se debe modificar.

Editar: como señalan Mark, GMan y Pavel, también existe una diferencia cuando el operador de dirección se utiliza en cualquiera de estas variables. Por ejemplo, & pmessage devuelve un puntero de tipo char **, o un puntero a un puntero a caracteres, mientras que & amessage devuelve un puntero de tipo char (*) [16], o un puntero a una matriz de 16 caracteres (que, como un char **, necesita ser desreferenciado dos veces como señala litb).

Aquí hay un mapa de memoria hipotética, que muestra los resultados de las dos declaraciones:

  0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x00008000: 'n' 'o' 'w' ' ' 'i' 's' ' ' 't' 0x00008008: 'h' 'e' ' ' 't' 'i' 'm' 'e' '\0' ... amessage: 0x00500000: 'n' 'o' 'w' ' ' 'i' 's' ' ' 't' 0x00500008: 'h' 'e' ' ' 't' 'i' 'm' 'e' '\0' pmessage: 0x00500010: 0x00 0x00 0x80 0x00 

El literal de cadena “ahora es el momento” se almacena como una matriz de 16 elementos de char en la dirección de memoria 0x00008000. Esta memoria no puede escribirse; lo mejor es suponer que no es así. Nunca debe intentar modificar el contenido de un literal de cadena.

La declaracion

 char amessage[] = "now is the time"; 

asigna una matriz de 16 elementos de char en la dirección de memoria 0x00500000 y copia el contenido de la cadena literal en ella. Esta memoria es escribible; puedes cambiar los contenidos de un mensaje al contenido de tu corazón:

 strcpy(amessage, "the time is now"); 

La declaracion

 char *pmessage = "now is the time"; 

asigna un único puntero a char en la dirección de memoria 0x00500010 y copia la dirección del literal de cadena.

Como pmessage apunta al literal de cadena, no debe usarse como argumento para las funciones que necesitan modificar el contenido de la cadena:

 strcpy(amessage, pmessage); /* OKAY */ strcpy(pmessage, amessage); /* NOT OKAY */ strtok(amessage, " "); /* OKAY */ strtok(pmessage, " "); /* NOT OKAY */ scanf("%15s", amessage); /* OKAY */ scanf("%15s", pmessage); /* NOT OKAY */ 

y así. Si cambió el mensaje para señalar a un mensaje:

 pmessage = amessage; 

luego se puede usar en cualquier lugar donde se pueda usar un mensaje.

Una matriz contiene los elementos. Un puntero apunta a ellos.

El primero es una forma corta de decir

 char amessage[16]; amessage[0] = 'n'; amessage[1] = 'o'; ... amessage[15] = '\0'; 

Es decir, es una matriz que contiene todos los personajes. La inicialización especial lo inicializa y lo determina automáticamente. Los elementos de la matriz son modificables; puede sobrescribir caracteres en ella.

La segunda forma es un puntero que apunta a los personajes. Almacena los caracteres no directamente. Dado que la matriz es una cadena literal, no puede tomar el puntero y escribir donde apunta

 char *pmessage = "now is the time"; *pmessage = 'p'; /* undefined behavior! */ 

Este código probablemente se bloquee en tu caja. Pero puede hacer lo que quiera porque su comportamiento no está definido.

No puedo agregar de manera útil las otras respuestas, pero comentaré que en Deep C Secrets , Peter van der Linden cubre este ejemplo en detalle. Si hace este tipo de preguntas, creo que le encantará este libro.


PD: puede asignar un nuevo valor a pmessage . No puede asignar un nuevo valor a un amessage ; es inmutable

Si se define una matriz para que su tamaño esté disponible en el momento de la statement, sizeof(p)/sizeof(type-of-array) devolverá el número de elementos en la matriz.

Junto con la memoria para la cadena “ahora es el momento” que se asigna en dos lugares diferentes, también debe tener en cuenta que el nombre de la matriz actúa como un valor de puntero en oposición a una variable de puntero que es pmessage. La principal diferencia es que la variable del puntero se puede modificar para apuntar a otro lugar y la matriz no.

 char arr[] = "now is the time"; char *pchar = "later is the time"; char arr2[] = "Another String"; pchar = arr2; //Ok, pchar now points at "Another String" arr = arr2; //Compiler Error! The array name can be used as a pointer VALUE //not a pointer VARIABLE 

Un puntero es solo una variable que contiene una dirección de memoria. Tenga en cuenta que está jugando con “cadenas literales”, que es otro problema. Diferencias explicadas en línea: Básicamente:

 #include  int main () { char amessage[] = "now is the time"; /* Attention you have created a "string literal" */ char *pmessage = "now is the time"; /* You are REUSING the string literal */ /* About arrays and pointers */ pmessage = NULL; /* All right */ amessage = NULL; /* Comstacktion ERROR!! */ printf ("%d\n", sizeof (amessage)); /* Size of the string literal*/ printf ("%d\n", sizeof (pmessage)); /* Size of pmessage is platform dependent - size of memory bus (1,2,4,8 bytes)*/ printf ("%p, %p\n", pmessage, &pmessage); /* These values are different !! */ printf ("%p, %p\n", amessage, &amessage); /* These values are THE SAME!!. There is no sense in retrieving "&amessage" */ /* About string literals */ if (pmessage == amessage) { printf ("A string literal is defined only once. You are sharing space"); /* Demostration */ "now is the time"[0] = 'W'; printf ("You have modified both!! %s == %s \n", amessage, pmessage); } /* Hope it was useful*/ return 0; } 

La primera forma ( amessage ) define una variable (una matriz) que contiene una copia de la cadena "now is the time" .

La segunda forma ( pmessage ) define una variable (un puntero) que vive en una ubicación diferente a cualquier copia de la cadena "now is the time" .

Prueba este progtwig:

 #include  #include  int main (int argc, char *argv []) { char amessage [] = "now is the time"; char *pmessage = "now is the time"; printf("&amessage : %#016"PRIxPTR"\n", (uintptr_t)&amessage); printf("&amessage[0]: %#016"PRIxPTR"\n", (uintptr_t)&amessage[0]); printf("&pmessage : %#016"PRIxPTR"\n", (uintptr_t)&pmessage); printf("&pmessage[0]: %#016"PRIxPTR"\n", (uintptr_t)&pmessage[0]); printf("&\"now is the time\": %#016"PRIxPTR"\n", (uintptr_t)&"now is the time"); return 0; } 

Verás que while &amessage es igual a &amessage[0] , esto no es cierto para &pmessage y &pmessage[0] . De hecho, verá que la cadena almacenada en un amessage vive en la stack, mientras que la cadena apuntada por el pmessage vive en otra parte.

El último printf muestra la dirección del literal de la cadena. Si su comstackdor realiza “agrupación de cadenas”, solo habrá una copia de la cadena “ahora es la hora”, y verá que su dirección no es la misma que la dirección de un amessage . Esto es porque amessage obtiene una copia de la cadena cuando se inicializa.

Al final, el punto es que amessage almacena la cadena en su propia memoria (en la stack, en este ejemplo), mientras que el pmessage apunta a la cadena que está almacenada en otra parte.

El segundo asigna la cadena en alguna sección de solo lectura del ELF. Pruebe lo siguiente:

 #include  int main(char argc, char** argv) { char amessage[] = "now is the time"; char *pmessage = "now is the time"; amessage[3] = 'S'; printf("%s\n",amessage); pmessage[3] = 'S'; printf("%s\n",pmessage); } 

y obtendrás un segfault en la segunda asignación (pmessage [3] = ‘S’).

diferencias entre puntero char y matriz

Borrador C99 N1256

Hay dos usos completamente diferentes de los literales de matriz:

  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 inicializarse mediante 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 el conjunto es de tamaño desconocido) inicializan los elementos del conjunto.

    Entonces este es solo un atajo para:

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

    Al igual que cualquier otra matriz regular, c puede modificarse.

  2. En todos lados: genera un:

    • sin nombre
    • array of char ¿Cuál es el tipo de literales de cadena en C y C ++?
    • con almacenamiento estático
    • eso le da a UB si se modifica

    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 el reparto implícito 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 una cadena literal o literal. La secuencia de caracteres multibyte se usa luego para inicializar una matriz de duración de almacenamiento estático y longitud 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 apropiados. Si el progtwig intenta modificar dicha matriz, el comportamiento no está definido.

6.7.8 / 32 “Inicialización” da un ejemplo directo:

EJEMPLO 8: La statement

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

define objetos de matriz char “simples” s y t cuyos elementos se inicializan con literales de cadena de caracteres.

Esta statement es idéntica a

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

El contenido de las matrices es modificable. Por otro lado, la statement

 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 un literal de cadena de caracteres. Si se intenta utilizar p para modificar el contenido de la matriz, el comportamiento no está definido.

Implementación GCC 4.8 x86-64 ELF

Progtwig:

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

Comstackr y descomstackr:

 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 de .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 tanto, se almacena en la stack (en relación con %rbp ).

Sin embargo, .text en cuenta que el script del enlazador predeterminado coloca .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 

Las respuestas anteriores deben haber respondido a su pregunta. Pero me gustaría sugerirle que lea el párrafo “Embryonic C” en The Development of C Language, escrito por Sir Dennis Ritchie.

Para esta línea: char amessage [] = “now is the time”;

el comstackdor evaluará los usos de un mensaje como un puntero al inicio de la matriz que contiene los caracteres “ahora es el momento”. El comstackdor asigna memoria para “ahora es el momento” y la inicializa con la cadena “ahora es el momento”. Usted sabe dónde está almacenado ese mensaje porque un mensaje siempre se refiere al comienzo de ese mensaje. amessage no se le puede dar un nuevo valor; no es una variable, es el nombre de la cadena “now is the time”.

Esta línea: char * pmessage = “ahora es el momento”;

declara una variable, mensaje que se inicializa (dado un valor inicial) de la dirección inicial de la cadena “ahora es la hora”. A diferencia de un mensaje, el mensaje puede recibir un nuevo valor. En este caso, como en el caso anterior, el comstackdor también almacena “ahora es el momento” en otro lugar de la memoria. Por ejemplo, esto hará que el mensaje apunte a la ‘i’ que comienza ‘es la hora’. pmessage = pmessage + 4;

Aquí está mi resumen de las diferencias clave entre arreglos e indicadores, que hice para mí:

 //ATTENTION: //Pointer depth 1 int marr[] = {1,13,25,37,45,56}; // array is implemented as a Pointer TO THE FIRST ARRAY ELEMENT int* pmarr = marr; // don't use & for assignment, because same pointer depth. Assigning Pointer = Pointer makes them equal. So pmarr points to the first ArrayElement. int* point = (marr + 1); // ATTENTION: moves the array-pointer in memory, but by sizeof(TYPE) and not by 1 byte. The steps are equal to the type of the array-elements (here sizeof(int)) //Pointer depth 2 int** ppmarr = &pmarr; // use & because going one level deeper. So use the address of the pointer. //TYPES //array and pointer are different, which can be seen by checking their types std::cout < < "type of marr is: " << typeid(marr).name() << std::endl; // int* so marr gives a pointer to the first array element std::cout << "type of &marr is: " << typeid(&marr).name() << std::endl; // int (*)[6] so &marr gives a pointer to the whole array std::cout << "type of pmarr is: " << typeid(pmarr).name() << std::endl; // int* so pmarr gives a pointer to the first array element std::cout << "type of &pmarr is: " << typeid(&pmarr).name() << std::endl; // int** so &pmarr gives a pointer to to pointer to the first array elelemt. Because & gets us one level deeper. 

Una matriz es un puntero const. No puede actualizar su valor y hacer que apunte a otro lugar. Mientras que para un puntero puedes hacerlo.