Actualmente estoy aprendiendo consejos y mi profesor proporcionó este código como ejemplo:
//We cannot predict the behavior of this program!
#include <iostream>
using namespace std;
int main()
{
char * s = "My String";
char s2[] = {'a', 'b', 'c', '\0'};
cout << s2 << endl;
return 0;
}
Escribió en los comentarios que no podemos predecir el comportamiento del programa. Pero, ¿qué es exactamente lo que lo hace impredecible? No veo nada malo en ello.
s
programa, si es aceptado por algún compilador, formalmente tiene un comportamiento impredecible.Respuestas:
El comportamiento del programa es inexistente porque está mal formado.
Esto es ilegal Antes de 2011, había estado en desuso durante 12 años.
La línea correcta es:
Aparte de eso, el programa está bien. ¡Tu profesor debería beber menos whisky!
fuente
La respuesta es: depende del estándar C ++ con el que compile. Todo el código está perfectamente formado en todos los estándares ‡ con la excepción de esta línea:
Ahora, el literal de cadena tiene tipo
const char[10]
y estamos tratando de inicializar un puntero que no sea constante. Para todos los demás tipos distintos de lachar
familia de cadenas literales, dicha inicialización siempre fue ilegal. Por ejemplo:Sin embargo, en pre-C ++ 11, para cadenas literales, hubo una excepción en §4.2 / 2:
Entonces, en C ++ 03, el código está perfectamente bien (aunque en desuso) y tiene un comportamiento claro y predecible.
En C ++ 11, ese bloque no existe; no existe tal excepción para los literales de cadena convertidos a
char*
, por lo que el código está tan mal formado como elint*
ejemplo que acabo de proporcionar. El compilador está obligado a emitir un diagnóstico, e idealmente en casos como este que son claras violaciones del sistema de tipos C ++, esperaríamos que un buen compilador no solo se ajuste a este respecto (por ejemplo, emitiendo una advertencia) sino que falle. total.Idealmente, el código no debería compilarse, pero lo hace tanto en gcc como en clang (supongo que porque probablemente hay mucho código que se rompería con poca ganancia, a pesar de que este tipo de agujero del sistema ha estado en desuso durante más de una década). El código está mal formado y, por lo tanto, no tiene sentido razonar sobre cuál podría ser el comportamiento del código. Pero considerando este caso específico y la historia de que se permitió previamente, no creo que sea un tramo irrazonable interpretar el código resultante como si fuera un implícito
const_cast
, algo como:Con eso, el resto del programa está perfectamente bien, ya que nunca volverás a tocar
s
. Leer unconst
objeto creado a través de un noconst
puntero está perfectamente bien. Escribir unconst
objeto creado a través de dicho puntero es un comportamiento indefinido:Como no hay modificaciones en
s
ninguna parte de su código, el programa está bien en C ++ 03, debería fallar al compilar en C ++ 11 pero lo hace de todos modos, y dado que los compiladores lo permiten, todavía no hay un comportamiento indefinido en él † . Teniendo en cuenta que los compiladores siguen interpretando [incorrectamente] las reglas de C ++ 03, no veo nada que pueda conducir a un comportamiento "impredecible". Sins
embargo, escriba a y todas las apuestas están canceladas. Tanto en C ++ 03 como en C ++ 11.† Aunque, nuevamente, por definición, el código mal formado no genera expectativas de comportamiento razonable
‡ Excepto que no, vea la respuesta de Matt McNabb
fuente
Otras respuestas han cubierto que este programa está mal formado en C ++ 11 debido a la asignación de una
const char
matriz achar *
.Sin embargo, el programa también estaba mal diseñado antes de C ++ 11.
Las
operator<<
sobrecargas están en<ostream>
. El requisito deiostream
incluirostream
se agregó en C ++ 11.Históricamente, la mayoría de las implementaciones habían
iostream
incluido deostream
todos modos, quizás para facilitar la implementación o quizás para proporcionar una mejor QoI.Pero sería adecuado
iostream
definir solo laostream
clase sin definir lasoperator<<
sobrecargas.fuente
Lo único ligeramente incorrecto que veo con este programa es que se supone que no debes asignar un literal de cadena a un
char
puntero mutable , aunque esto a menudo se acepta como una extensión del compilador.De lo contrario, este programa me parece bien definido:
cout << s2
) están bien definidas.operator<<
con achar*
(o aconst char*
).#include <iostream>
incluye<ostream>
, que a su vez defineoperator<<(ostream&, const char*)
, por lo que todo parece estar en su lugar.fuente
No se puede predecir el comportamiento del compilador, por las razones mencionadas anteriormente. ( Debería fallar al compilar, pero puede que no).
Si la compilación tiene éxito, entonces el comportamiento está bien definido. Sin duda, puede predecir el comportamiento del programa.
Si no se compila, no hay programa. En un lenguaje compilado, el programa es el ejecutable, no el código fuente. Si no tiene un ejecutable, no tiene un programa y no puede hablar sobre el comportamiento de algo que no existe.
Entonces diría que la declaración de su profesor es incorrecta. No se puede predecir el comportamiento del compilador cuando se enfrenta a este código, pero eso es distinto del comportamiento del programa . Entonces, si va a picar liendres, será mejor que se asegure de tener razón. O, por supuesto, es posible que lo haya citado mal y el error esté en la traducción de lo que dijo.
fuente
Como han señalado otros, el código es ilegítimo en C ++ 11, aunque era válido en versiones anteriores. En consecuencia, se requiere un compilador para C ++ 11 para emitir al menos un diagnóstico, pero el comportamiento del compilador o del resto del sistema de compilación no se especifica más allá de eso. Nada en el Estándar prohibiría a un compilador salir abruptamente en respuesta a un error, dejando un archivo de objeto parcialmente escrito que un enlazador podría pensar que es válido, produciendo un ejecutable roto.
Aunque un buen compilador siempre debe asegurarse antes de salir de que cualquier archivo objeto que se espera que haya producido será válido, inexistente o reconocible como inválido, tales cuestiones quedan fuera de la jurisdicción del Estándar. Si bien históricamente ha habido (y aún puede haber) algunas plataformas en las que una compilación fallida puede dar como resultado archivos ejecutables de apariencia legítima que se bloquean de manera arbitraria cuando se cargan (y he tenido que trabajar con sistemas donde los errores de enlace a menudo tenían ese comportamiento) , No diría que las consecuencias de los errores de sintaxis son generalmente impredecibles. En un buen sistema, un intento de compilación generalmente producirá un ejecutable con el mejor esfuerzo del compilador en la generación de código, o no producirá un ejecutable en absoluto. Algunos sistemas dejarán atrás el antiguo ejecutable después de una compilación fallida,
Mi preferencia personal sería que los sistemas basados en disco cambien el nombre del archivo de salida, para tener en cuenta las raras ocasiones en las que ese ejecutable sería útil y al mismo tiempo evitar la confusión que puede resultar de creer erróneamente que uno está ejecutando un código nuevo, y para la programación integrada sistemas para permitir que un programador especifique para cada proyecto un programa que debe cargarse si un ejecutable válido no está disponible con el nombre normal [idealmente algo que indique con seguridad la falta de un programa utilizable]. Un conjunto de herramientas de sistemas embebidos generalmente no tendría forma de saber qué debería hacer tal programa, pero en muchos casos alguien que escriba código "real" para un sistema tendrá acceso a algún código de prueba de hardware que podría adaptarse fácilmente a la propósito. No sé si he visto el comportamiento de cambio de nombre, sin embargo,
fuente