que - punteros en c++ ejercicios resueltos pdf
C: diferencias entre puntero char y array (14)
Esta pregunta ya tiene una respuesta aquí:
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?
diferencias entre puntero char y matriz
Borrador C99 N1256
Hay dos usos completamente diferentes de los literales de matriz:
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.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[]
achar *
, 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 programa intenta modificar dicha matriz, el comportamiento no está definido.
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 char "simples"
s
yt
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 las matrices es modificable. 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 un literal de cadena de caracteres. Si se intenta utilizarp
para modificar el contenido de la matriz, el comportamiento no está definido.
Implementación GCC 4.8 x86-64 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*
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 pila (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
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.
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.
El segundo asigna la cadena en alguna sección de solo lectura del ELF. Pruebe lo siguiente:
#include <stdio.h>
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'').
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 pila del alcance actual, mientras que:
char *pmessage = "now is the time";
Define un puntero que vive en el espacio de la pila del alcance 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 pila del alcance 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).
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
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 programa:
#include <inttypes.h>
#include <stdio.h>
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 pila, 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 compilador 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 pila, en este ejemplo), mientras que el pmessage
apunta a la cadena que está almacenada en otra parte.
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.
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
Para esta línea: char amessage [] = "now is the time";
el compilador evaluará los usos de un mensaje como un puntero al inicio de la matriz que contiene los caracteres "ahora es el momento". El compilador 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 compilador 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;
Si se define una matriz para que su tamaño esté disponible en el momento de la declaración, sizeof(p)/sizeof(type-of-array)
devolverá el número de elementos en la matriz.
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 <stdio.h>
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; /* Compilation 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;
}
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.
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.