J'apprends actuellement des pointeurs et mon professeur a fourni ce morceau de code à titre d'exemple:
//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;
}
Il a écrit dans les commentaires que nous ne pouvons pas prédire le comportement du programme. Mais qu'est-ce qui le rend exactement imprévisible? Je ne vois rien de mal à cela.
s
, le programme, s'il est accepté par un compilateur, a formellement un comportement imprévisible.Réponses:
Le comportement du programme est inexistant, car mal formé.
C'est illégal. Avant 2011, il était obsolète depuis 12 ans.
La ligne correcte est:
A part ça, le programme est très bien. Votre professeur devrait boire moins de whisky!
la source
La réponse est: cela dépend de la norme C ++ avec laquelle vous compilez. Tout le code est parfaitement bien formé dans toutes les normes ‡ à l'exception de cette ligne:
Maintenant, le littéral de chaîne a un type
const char[10]
et nous essayons d'initialiser un pointeur non const vers lui. Pour tous les autres types autres que lachar
famille des chaînes littérales, une telle initialisation était toujours illégale. Par exemple:Cependant, dans le pré-C ++ 11, pour les littéraux de chaîne, il y avait une exception dans §4.2 / 2:
Ainsi, en C ++ 03, le code est parfaitement correct (bien que déconseillé) et présente un comportement clair et prévisible.
En C ++ 11, ce bloc n'existe pas - il n'y a pas d'exception pour les chaînes littérales converties en
char*
, et donc le code est tout aussi mal formé que l'int*
exemple que je viens de fournir. Le compilateur est obligé d'émettre un diagnostic, et idéalement dans des cas comme celui-ci qui sont des violations manifestes du système de type C ++, nous nous attendrions à ce qu'un bon compilateur ne soit pas seulement conforme à cet égard (par exemple en émettant un avertissement) mais qu'il échoue carrément.Le code ne devrait idéalement pas compiler - mais le fait à la fois sur gcc et clang (je suppose qu'il y a probablement beaucoup de code là-bas qui serait cassé avec peu de gain, bien que ce type de trou système soit obsolète depuis plus d'une décennie). Le code est mal formé et il est donc illogique de raisonner sur ce que pourrait être le comportement du code. Mais compte tenu de ce cas spécifique et de l'historique de son autorisation, je ne pense pas qu'il soit déraisonnable d'interpréter le code résultant comme s'il s'agissait d'un implicite
const_cast
, quelque chose comme:Avec cela, le reste du programme est parfaitement bien, car vous ne touchez
s
plus jamais . La lecture d' unconst
objet créé via un non-const
pointeur est parfaitement OK. L'écriture d' unconst
objet créé via un tel pointeur est un comportement indéfini:Comme il n'y a aucune modification via
s
n'importe où dans votre code, le programme fonctionne bien en C ++ 03, devrait échouer à compiler en C ++ 11 mais le fait quand même - et étant donné que les compilateurs le permettent, il n'y a toujours pas de comportement indéfini † . En admettant que les compilateurs interprètent toujours [incorrectement] les règles de C ++ 03, je ne vois rien qui conduirait à un comportement "imprévisible". Écrivez às
cependant, et tous les paris sont ouverts. Dans C ++ 03 et C ++ 11.† Bien que, encore une fois, par définition, un code mal formé ne donne aucune attente d'un comportement raisonnable
‡ Sauf non, voir la réponse de Matt McNabb
la source
D'autres réponses ont couvert que ce programme est mal formé en C ++ 11 en raison de l'affectation d'un
const char
tableau à unchar *
.Cependant, le programme était également mal formé avant C ++ 11.
Les
operator<<
surcharges sont là<ostream>
. L'obligationiostream
d'inclure aostream
été ajoutée dans C ++ 11.Historiquement, la plupart des implémentations
iostream
incluaient deostream
toute façon, peut-être pour faciliter la mise en œuvre ou peut-être pour fournir une meilleure QoI.Mais il serait conforme
iostream
de ne définir que laostream
classe sans définir lesoperator<<
surcharges.la source
La seule chose légèrement erronée que je vois avec ce programme est que vous n'êtes pas censé attribuer une chaîne littérale à un
char
pointeur mutable , bien que cela soit souvent accepté comme une extension de compilateur.Sinon, ce programme me paraît bien défini:
cout << s2
) sont bien définies.operator<<
avec achar*
(ou aconst char*
).#include <iostream>
inclut<ostream>
, qui à son tour définitoperator<<(ostream&, const char*)
, donc tout semble être en place.la source
Vous ne pouvez pas prédire le comportement du compilateur, pour les raisons indiquées ci-dessus. (La compilation devrait échouer, mais peut-être pas.)
Si la compilation réussit, le comportement est bien défini. Vous pouvez certainement prédire le comportement du programme.
Si la compilation échoue, il n'y a pas de programme. Dans un langage compilé, le programme est l'exécutable, pas le code source. Si vous n'avez pas d'exécutable, vous n'avez pas de programme et vous ne pouvez pas parler du comportement de quelque chose qui n'existe pas.
Je dirais donc que la déclaration de votre prof est fausse. Vous ne pouvez pas prédire le comportement du compilateur face à ce code, mais cela est distinct du comportement du programme . Donc, s'il choisit des lentes, il ferait mieux de s'assurer qu'il a raison. Ou, bien sûr, vous l'avez peut-être mal cité et l'erreur réside dans votre traduction de ce qu'il a dit.
la source
Comme d'autres l'ont noté, le code est illégitime sous C ++ 11, bien qu'il était valide sous les versions antérieures. Par conséquent, un compilateur pour C ++ 11 est requis pour émettre au moins un diagnostic, mais le comportement du compilateur ou du reste du système de génération n'est pas spécifié au-delà. Rien dans le Standard n'interdirait à un compilateur de quitter brusquement en réponse à une erreur, laissant un fichier objet partiellement écrit qu'un éditeur de liens pourrait penser être valide, produisant un exécutable cassé.
Bien qu'un bon compilateur doive toujours s'assurer avant de quitter que tout fichier objet qu'il est censé avoir produit sera valide, inexistant ou reconnaissable comme invalide, ces problèmes ne relèvent pas de la compétence de la norme. Bien qu'il y ait historiquement eu (et peut-être encore) des plates-formes sur lesquelles une compilation échouée peut entraîner des fichiers exécutables d'apparence légitime qui plantent de manière arbitraire lors du chargement (et j'ai dû travailler avec des systèmes où les erreurs de lien avaient souvent un tel comportement) , Je ne dirais pas que les conséquences des erreurs de syntaxe sont généralement imprévisibles. Sur un bon système, une tentative de construction produira généralement un exécutable avec les meilleurs efforts d'un compilateur pour la génération de code, ou ne produira pas du tout un exécutable. Certains systèmes laisseront l'ancien exécutable après un échec de compilation,
Ma préférence personnelle serait que les systèmes basés sur disque renomment le fichier de sortie, pour permettre les rares occasions où cet exécutable serait utile tout en évitant la confusion qui peut résulter de la croyance erronée que l'on exécute un nouveau code, et pour la programmation intégrée systèmes pour permettre à un programmeur de spécifier pour chaque projet un programme qui devrait être chargé si un exécutable valide n'est pas disponible sous le nom normal [idéalement quelque chose qui indique en toute sécurité l'absence d'un programme utilisable]. Un ensemble d'outils de systèmes embarqués n'aurait généralement aucun moyen de savoir ce qu'un tel programme devrait faire, mais dans de nombreux cas, quelqu'un qui écrit du code "réel" pour un système aura accès à un code de test matériel qui pourrait facilement être adapté au objectif. Je ne sais pas si j'ai vu le comportement de changement de nom, cependant,
la source