¿Es válido modificar una cadena apuntada por un puntero?

Aquí hay un ejemplo simple de un progtwig que concatena dos cadenas.

#include  void strcat(char *s, char *t); void strcat(char *s, char *t) { while (*s++ != '\0'); s--; while ((*s++ = *t++) != '\0'); } int main() { char *s = "hello"; strcat(s, " world"); while (*s != '\0') { putchar(*s++); } return 0; } 

Me pregunto por qué funciona. En main (), tengo un puntero a la cadena “hola”. De acuerdo con el libro de K&R, modificar una cadena como esa es un comportamiento indefinido. Entonces, ¿por qué el progtwig es capaz de modificarlo añadiendo “mundo”? ¿O se considera que la adición no modifica?

Un comportamiento indefinido significa que un comstackdor puede emitir código que hace cualquier cosa. Trabajar es un subconjunto de indefinido.

I + 1 en MSN, pero en cuanto a por qué funciona, es porque aún no ha aparecido nada para llenar el espacio detrás de la cadena. Declare unas cuantas variables más, agregue algo de complejidad y comenzará a ver algunas alteraciones.

Tal vez sorprendentemente, su comstackdor ha asignado el literal "hello" a los datos inicializados de lectura / escritura en lugar de los datos inicializados de solo lectura. Su tarea incluye lo que sea adyacente a ese lugar, pero su progtwig es lo suficientemente pequeño y simple como para que no vea los efectos. (Colóquelo en un bucle for y vea si está golpeando el literal " world" ).

Falla en Ubuntu x64 porque gcc coloca literales de cadena en datos de solo lectura, y cuando intenta escribir, la MMU de hardware se opone.

Tuviste suerte esta vez.
Especialmente en el modo de depuración, algunos comstackdores colocarán memoria de reserva (a menudo llena de algún valor obvio) alrededor de las declaraciones para que pueda encontrar código como este.

También depende de cómo se declara el puntero. Por ejemplo, puede cambiar ptr, y qué ptr apunta a:

 char * ptr; 

Puede cambiar lo que apunta ptr, pero no ptr:

 char const * ptr; 

Puede cambiar ptr, pero no lo que ptr apunta a:

 const char * ptr; 

No se puede cambiar nada:

 const char const * ptr; 

Según la especificación de C99 (C99: TC3, 6.4.5, §5), los literales de cadena son

[…] se usa para inicializar una matriz de duración de almacenamiento estático y la longitud suficiente para contener la secuencia. […]

lo que significa que tienen el tipo char [] , es decir, la modificación es posible en principio. Por qué no deberías hacerlo se explica en §6:

No se especifica si estas matrices son distintas siempre que sus elementos tengan los valores adecuados. Si el progtwig intenta modificar dicha matriz, el comportamiento es indefinido.

Diferentes literales de cadena con el mismo contenido pueden, pero no tienen que, asignarse a la misma ubicación de memoria. Como el comportamiento no está definido, los comstackdores son libres de ponerlos en secciones de solo lectura para fallar limpiamente en lugar de presentar fonts de error posiblemente difíciles de detectar.

Me pregunto por qué funciona

No lo hace Causa una falla de segmentación en Ubuntu x64; para que el código funcione no debería funcionar en su máquina .

Al mover los datos modificados a la stack se obtiene la protección del área de datos en linux:

 int main() { char b[] = "hello"; char c[] = " "; char *s = b; strcat(s, " world"); puts(b); puts(c); return 0; } 

A pesar de que solo está seguro, ya que ‘mundo’ se ajusta a los espacios no utilizados entre los datos de la stack, cambie b por “hola a” y Linux detectará la corrupción de la stack:

 *** stack smashing detected ***: bin/clobber terminated 

El comstackdor le permite modificar s porque lo ha marcado incorrectamente como no constante: un puntero a una cadena estática como esa debería ser

 const char *s = "hello"; 

Con la falta del modificador const, básicamente ha desactivado la seguridad que le impide escribir en la memoria en la que no debería escribir. C hace muy poco para evitar que te dispares en el pie. En este caso, tuvo suerte y solo rozó su dedo meñique.

s apunta a un poco de memoria que contiene “hola”, pero no estaba destinado a contener más que eso. Esto significa que es muy probable que esté sobrescribiendo otra cosa. Eso es muy peligroso, aunque parezca funcionar.

Dos observaciones:

  1. El * in * s– no es necesario. s– sería suficiente, porque solo desea disminuir el valor.
  2. No necesitas escribir strcat tú mismo. Ya existe (probablemente lo sabías, pero te lo digo de todos modos :-)).