¿Por qué los literales compuestos en C son modificables?

6 minutos de lectura

avatar de usuario
hgiesel

Uno suele asociar ‘no modificable’ con el término literal

char* str = "Hello World!";
*str="B";  // Bus Error!

Sin embargo, al usar literales compuestos, descubrí rápidamente que son completamente modificables (y al observar el código de máquina generado, se ve que se colocan en la pila):

char* str = (char[]){"Hello World"};
*str="B";  // A-Okay!

estoy compilando con clang-703.0.29. ¿No deberían esos dos ejemplos generar exactamente el mismo código de máquina? ¿Es un literal compuesto realmente un literal, si es modificable?

EDITAR: un ejemplo aún más corto sería:

"Hello World"[0] = 'B';  // Bus Error!
(char[]){"Hello World"}[0] = 'B';  // Okay!

  • Ni siquiera estoy seguro de que sea UB, nunca he mirado el oficial estándar de idiomapero dice en 6.5.2.5 en el punto 12, dice que (char[]){"abc"} está diseñado para ser modificable.

    – hgiesel

    17 de abril de 2016 a las 12:09


  • Tenga en cuenta que el ejemplo anterior en realidad no muestra el comportamiento de lvalue-literal (un ejemplo más claro sería algo así como (int){1} = 2;) – la principal diferencia entre sus dos fragmentos es que en el primero tiene un literal de cadena verdadero, mientras que en el segundo inicializar una matriz local con un literal de cadena: tendría el mismo comportamiento si solo modificara el primero para leer char str[] = ....

    – Leushenko

    17 de abril de 2016 a las 12:16


  • @Leushenko ¿Yo? En el caso literal compuesto, inicializo str con un puntero a su primer carácter. Cuando yo digo char str[] = …inicializo un literal de caracteres no modificable y copio su contenido en la matriz str en la pila

    – hgiesel

    17 de abril de 2016 a las 12:21


  • @hgiesel: Tienes razón, de hecho es un “objeto anónimo”. Puedes usar el const calificador para decirle explícitamente al compilador su intención. Tenga en cuenta que en C es responsabilidad del programador no romper este contrato. Incluso para literales de cadenano hay garantía de que se escriba no work (y C lo permite explícitamente como una extensión de implementación). Por lo tanto, no obtener un error no significa que sea un comportamiento definido. En caso de duda, lea la norma.

    – demasiado honesto para este sitio

    17 de abril de 2016 a las 12:21


  • @hgiesel: No. 1) El lenguaje C ni siquiera exige el uso de una pila (ni un montón, por cierto) y hay implementaciones que no lo hacen. 2) Hay un objeto asignado en alguna parte, más el literal de cadena para inicializarlo. Pero, ¿debe haber el mismo objeto asignado dos veces? Su ejemplo que usa una cadena no es bueno, ya que puede usar el literal directamente, pero sin embargo es válido.

    – demasiado honesto para este sitio

    17 de abril de 2016 a las 12:27


avatar de usuario
trucos

Un literal compuesto es un lvalue y los valores de sus elementos son modificables. En caso de

char* str = (char[]){"Hello World"};
*str="B";  // A-Okay!  

está modificando un literal compuesto que es legal.

C11-§6.5.2.5/4:

Si el nombre del tipo especifica una matriz de tamaño desconocido, el tamaño está determinado por la lista de inicializadores como se especifica en 6.7.9, y el tipo del literal compuesto es el del tipo de matriz completa. De lo contrario (cuando el nombre de tipo especifica un tipo de objeto), el tipo del literal compuesto es el especificado por el nombre de tipo. En cualquier caso, el resultado es un lvalue.

Como puede verse, el tipo de literal compuesto es un tipo de matriz completa y es lvalue, por lo tanto, es modificable a diferencia de literales de cadena

Standard también menciona que

§6.5.2.5/7:

Los literales de cadena y los literales compuestos con tipos calificados const no necesitan designar objetos distintos.101

Además dice:

11 EJEMPLO 4 Un literal compuesto de solo lectura se puede especificar a través de construcciones como:

(const float []){1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6}   

12 EJEMPLO 5 Las siguientes tres expresiones tienen diferentes significados:

"/tmp/fileXXXXXX"
(char []){"/tmp/fileXXXXXX"}
(const char []){"/tmp/fileXXXXXX"}

El primero siempre tiene una duración de almacenamiento estática y tiene un tipo de matriz de charpero no necesita ser modificable; los dos últimos tienen duración de almacenamiento automático cuando ocurren dentro del cuerpo de una función, y el el primero de estos dos es modificable.

13 EJEMPLO 6 Al igual que los literales de cadena, los literales compuestos calificados const se pueden colocar en la memoria de solo lectura e incluso se pueden compartir. Por ejemplo,

(const char []){"abc"} == "abc"

podría producir 1 si se comparte el almacenamiento de los literales.

  • Tenga en cuenta que esta pregunta está etiquetada con C99

    – nalzok

    17 de abril de 2016 a las 13:13

  • @sunqingyao; Si. Pero para este caso, la regla es casi la misma que C11.

    – trucos

    18 de abril de 2016 a las 4:58

  • Me parece molesto que (por lo que puedo decir) no haya sintaxis para los literales compuestos const-static, ya que los literales de cadena no son el único tipo de datos estáticos que a menudo tendrán un único punto de uso.

    – Super gato

    21 de abril de 2016 a las 18:37

La sintaxis literal compuesta es una expresión abreviada equivalente a una declaración local con un inicializador seguido de una referencia al objeto sin nombre así declarado:

char *str = (char[]){ "Hello World" };

es equivalente a:

char __unnamed__[] = { "Hello world" };
char *str = __unnamed__;

los __unnamed__ tiene almacenamiento automático y se define como modificable, se puede modificar mediante el puntero str inicializado para señalarlo.

En el caso de char *str = "Hello World!"; el objeto señalado por str no se supone que se modifique. De hecho, intentar modificarlo tiene un comportamiento indefinido.

El estándar C podría haber definido estos literales de cadena como si tuvieran tipo const char[] en lugar de char[]pero esto generaría muchas advertencias y errores en el código heredado.

Sin embargo, es aconsejable pasar una bandera al compilador para hacer implícitamente tales cadenas literales. const y hacer todo el proyecto const correcto, es decir: definir todos los argumentos de puntero que no se utilizan para modificar su objeto como const. Para gcc y clangla opción de línea de comando es -Wwrite-strings. También recomiendo encarecidamente habilitar muchas más advertencias y hacerlas fatales con -Wall -W -Werror.

  • Desafortunadamente para el código que estoy escribiendo, no, “la sintaxis literal compuesta es una expresión abreviada equivalente a una declaración local” no es cierto. Por ejemplo, puedes hacer <type> foo[<n>]; y obten <n> * sizeof(<type>) bytes de memoria de pila no inicializada en el alcance actual, pero no hay forma de tener literales compuestos no inicializados que solo asignan, (<type> [<n>]) {} no es estándar y la mayoría de los compiladores lo toman como {0}. Si hay una manera, házmelo saber. Esto hubiera sido muy útil.

    – usuario426

    19 de noviembre de 2021 a las 1:43


  • @ user426: esta es una pregunta diferente. Por cierto, su cita de mi respuesta está incompleta: escribí La sintaxis literal compuesta es una expresión abreviada equivalente a una declaración local con un inicializador […]. Si desea asignar algún espacio de pila no inicializado, puede usar alloca() en los sistemas donde está disponible.

    – chqrlie

    19 de noviembre de 2021 a las 13:58


¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad