¿Por qué las direcciones de memoria de los literales de cadena son tan diferentes de las de otros en Linux?

9 minutos de lectura

avatar de usuario
Ni idea

Noté que los literales de cadena tienen direcciones muy diferentes en la memoria que otras constantes y variables (SO Linux): tienen muchos ceros iniciales (no impresos).

Ejemplo:

const char *h = "Hi";
int i = 1;
printf ("%p\n", (void *) h);
printf ("%p\n", (void *) &i);

Producción:

0x400634
0x7fffc1ef1a4c

Sé que están almacenados en el .rodata parte del ejecutable. ¿Hay alguna forma especial en que el sistema operativo lo maneje después, de modo que los literales terminen en un área especial de la memoria (con ceros a la izquierda)? ¿Hay alguna ventaja de esa ubicación de memoria o hay algo especial al respecto?

  • Todo depende del sistema operativo donde carga el código y dónde asigna la pila.

    – Un tipo programador

    18 de noviembre de 2016 a las 12:55

  • Obviamente, está especificado por la implementación, pero los datos de RO (su literal) a menudo se cargan en páginas separadas marcadas para activación de excepción de escritura en modo protegido. Significado: escribir en él genera una excepción estructurada.

    – WhozCraig

    18/11/2016 a las 13:00


  • ¿Su pregunta es específicamente sobre Linux, sistemas alojados (con sistema operativo) en general, o también incluye sistemas independientes (generalmente integrados sin sistema operativo)? Si solo Linux, debe agregar [linux] etiqueta. Si hay algo más, por favor aclare.

    – usuario694733

    18 de noviembre de 2016 a las 13:01


  • Tu pregunta está al revés. Encontrarás eso todos las direcciones tienen ‘muchos ceros a la izquierda’ excepto direcciones de variables locales, que están en la pila, que se asigna en su caso desde la parte superior del espacio de direcciones hacia abajo.

    – usuario207421

    18/11/2016 a las 21:49

  • Para tener su cadena más como su int i = 1es posible que desee probar char h[] = "Hi"

    –Hagen von Eitzen

    20 de noviembre de 2016 a las 10:38

avatar de usuario
PSkocik

Así es como se distribuye la memoria de proceso en Linux (de http://www.thegeekstuff.com/2012/03/linux-processes-memory-layout/):

Disposición de la memoria de proceso de Linux

los .rodata sección es una subsección protegida contra escritura del Datos globales inicializados bloquear. (Una sección que DUENDE ejecutables designados .datos es su contraparte grabable para globales grabables inicializados a valores distintos de cero. Los globales grabables inicializados a ceros van a la .bss bloquear. Por globales aquí me refiero a variables globales y todo estático variables independientemente de la ubicación.)

La imagen debe explicar los valores numéricos de sus direcciones.

Si desea investigar más a fondo, en Linux puede inspeccionar el
/proc/$pid/mapas archivos virtuales que describen el diseño de la memoria de los procesos en ejecución. No obtendrá los nombres de las secciones ELF reservadas (que comienzan con un punto), pero puede adivinar de qué sección ELF se originó un bloque de memoria mirando sus indicadores de protección de memoria. Por ejemplo, correr

$ cat /proc/self/maps #cat's memory map

me da

00400000-0040b000 r-xp 00000000 fc:00 395465                             /bin/cat
0060a000-0060b000 r--p 0000a000 fc:00 395465                             /bin/cat
0060b000-0060d000 rw-p 0000b000 fc:00 395465                             /bin/cat
006e3000-00704000 rw-p 00000000 00:00 0                                  [heap]
3000000000-3000023000 r-xp 00000000 fc:00 3026487                        /lib/x86_64-linux-gnu/ld-2.19.so
3000222000-3000223000 r--p 00022000 fc:00 3026487                        /lib/x86_64-linux-gnu/ld-2.19.so
3000223000-3000224000 rw-p 00023000 fc:00 3026487                        /lib/x86_64-linux-gnu/ld-2.19.so
3000224000-3000225000 rw-p 00000000 00:00 0
3000400000-30005ba000 r-xp 00000000 fc:00 3026488                        /lib/x86_64-linux-gnu/libc-2.19.so
30005ba000-30007ba000 ---p 001ba000 fc:00 3026488                        /lib/x86_64-linux-gnu/libc-2.19.so
30007ba000-30007be000 r--p 001ba000 fc:00 3026488                        /lib/x86_64-linux-gnu/libc-2.19.so
30007be000-30007c0000 rw-p 001be000 fc:00 3026488                        /lib/x86_64-linux-gnu/libc-2.19.so
30007c0000-30007c5000 rw-p 00000000 00:00 0
7f49eda93000-7f49edd79000 r--p 00000000 fc:00 2104890                    /usr/lib/locale/locale-archive
7f49edd79000-7f49edd7c000 rw-p 00000000 00:00 0
7f49edda7000-7f49edda9000 rw-p 00000000 00:00 0
7ffdae393000-7ffdae3b5000 rw-p 00000000 00:00 0                          [stack]
7ffdae3e6000-7ffdae3e8000 r--p 00000000 00:00 0                          [vvar]
7ffdae3e8000-7ffdae3ea000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

El primero r-xp bloque definitivamente vino de .texto (código ejecutable), el primero r--p bloquear desde .rodatay lo siguiente rw– bloques de .bss y .datos. (Entre el montón y el bloque de pila hay bloques cargados desde bibliotecas enlazadas dinámicamente por el enlazador dinámico).


Nota: Para cumplir con el estándar, debe emitir el int* por "%p" a (void*) o bien el comportamiento es indefinido.

  • ¡Gracias, eso es útil! Pero si tengo múltiples procesos, esto todavía sucede. Entonces, ¿no los presenta uno tras otro, sino que toma todos los “Datos globales inicializados” de múltiples procesos y los almacena juntos?

    – Ni idea

    18 de noviembre de 2016 a las 13:21

  • @Noidea Diferentes procesos tienen diferentes espacios de direcciones. 0xDEADBEEF en un proceso (generalmente) no tiene ninguna relación con 0xDEADBEEF en otro. Hay algunas ventajas menores obvias en el diseño anterior relacionadas con la depuración y el crecimiento del bloque (para el bloque del montón en particular, aunque no es gran cosa fragmentar el montón con mmap si ya no puede crecer). Además, las direcciones mapeadas reales generalmente serán algo aleatorias por razones de seguridad.

    – PSkocik

    18/11/2016 a las 13:40

  • @Noidea: no combine direcciones físicas (correspondientes a direcciones en la RAM) con direcciones de memoria virtual (direcciones en el proceso). es el trabajo del unidad de gestión de memoria para convertir virtual a físico y todas las direcciones utilizadas por un proceso se traducen a través de la MMU. Cada proceso tiene sus propias tablas MMU, administradas por el sistema operativo.

    –Eric Torres

    18 de noviembre de 2016 a las 14:10

  • Los scripts de vinculación predeterminados de varias plataformas también se fusionan .rodata con .text.

    – Simón Richter

    19 de noviembre de 2016 a las 17:05

Eso es porque los literales de cadena tienen duración del almacenamiento estático. Es decir, vivirán durante todo el programa. Dichas variables pueden almacenarse en una ubicación de memoria especial que no se encuentra ni en el llamado montón ni en la pila. De ahí la diferencia de direcciones.

avatar de usuario
steve cumbre

Recuerde que donde un puntero es es diferente de donde un puntero puntos a. Una comparación más realista (manzanas con manzanas) sería

printf ("%p\n", (void *) &h);
printf ("%p\n", (void *) &i);

Sospecho que lo encontrarás h y p tienen direcciones similares. O, otra comparación más realista sería

static int si = 123;
int *ip = &si;
printf ("%p\n", (void *) h);
printf ("%p\n", (void *) ip);

Sospecho que encontrarás eso h y ip apuntan a una región similar de la memoria.

  • No, h ya es un puntero a char, por lo que &h no hace nada útil. Escritura h y &i es correcto ya que ambas son las direcciones de la cadena referida y int respectivamente.

    – subrayado_d

    18 de noviembre de 2016 a las 16:41

  • @underscore_d Creo que entendiste completamente mal la pregunta y mi respuesta, entonces. No hay nada “correcto” o “incorrecto” en escribir h y &i; el OP simplemente estaba desconcertado por qué las direcciones reales en su sistema eran tan diferentes. Mi punto era si escribes &h y &io h y ipes probable que vea más direcciones similares y este ejercicio (con suerte) lo ayudará a comprender por qué los números en h y &i son tan diferentes

    – Steve cumbre

    18 de noviembre de 2016 a las 16:52


  • @SteveSummit El puntero a un literal de cadena será otra variable de pila. Pero me preguntaba por qué la dirección de un literal de cadena es tan diferente de las direcciones de las variables de pila. No por qué las direcciones de dos variables de pila son similares;)

    – Ni idea

    18 de noviembre de 2016 a las 17:16


  • @Noidea Y ahora lo sabe, por las otras respuestas: porque los literales de cadena nunca se almacenan en la pila.

    – Steve cumbre

    18 de noviembre de 2016 a las 17:18

  • @SteveSummit bueno, ya sabía que no están en la pila, porque las direcciones son muy diferentes.

    – Ni idea

    18 de noviembre de 2016 a las 17:29

Tenga en cuenta que los literales son variables de solo lectura y, además, existe un concepto de grupo de literales. Lo que es el conjunto de literales es una colección de literales únicos del programa, donde las constantes duplicadas se descartan a medida que las referencias se fusionan en una sola.

Hay un grupo de literales para cada fuente y, según la sofisticación del programa link/bind, los grupos de literales se pueden colocar uno al lado del otro para crear un .rodata.

Tampoco hay garantía de que el grupo literal esté protegido de solo lectura. El lenguaje a través de los diseños del compilador lo trata como tal.

Considere mi fragmento de código. podría tener

const char *cp=”hola mundo”;
const char *cp1=”hola mundo”;

El buen compilador reconocerá que dentro de ese código fuente, los literales de solo lectura cp, cp1apuntan a cadenas idénticas y harán que cp1 apunte al literal de cp, descartando el segundo.

Un punto más. El grupo literal puede ser un múltiplo de 256 bytes o un valor diferente. Si los datos del grupo tienen menos de 256 bytes, la holgura se completará con ceros hexadecimales.

Diferentes compiladores, siguen estándares comunes de desarrollo, permitiendo un módulo compilado con Cpara ser enlazado con un módulo compilado con lenguaje ensamblador u otro idioma. Los dos grupos de literales se colocan consecutivamente en .rodata.

printf ("%p\n", h); // h is the address of "Hi", which is in the rodata or other segments of the application.
printf ("%p\n", &i); // I think "i" is not a global variable, so &i is in the stack of main. The stack address is by convention in the top area of the memory space of the process.

  • Esto no parece responder a la pregunta que se hizo. Como recordatorio, la pregunta era “¿El sistema operativo lo maneja de manera especial? ¿Hay alguna ventaja en esta forma de manejarlo?” Su respuesta no parece abordar esas preguntas. ¿Le gustaría editar su respuesta para abordar más directamente lo que se preguntó?

    – DW

    18 de noviembre de 2016 a las 15:58

  • Esto no parece responder a la pregunta que se hizo. Como recordatorio, la pregunta era “¿El sistema operativo lo maneja de manera especial? ¿Hay alguna ventaja en esta forma de manejarlo?” Su respuesta no parece abordar esas preguntas. ¿Le gustaría editar su respuesta para abordar más directamente lo que se preguntó?

    – DW

    18 de noviembre de 2016 a las 15:58

¿Ha sido útil esta solución?