No momento, estou aprendendo dicas e meu professor forneceu este trecho de código como exemplo:
//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;
}
Ele escreveu nos comentários que não podemos prever o comportamento do programa. O que exatamente o torna imprevisível? Eu não vejo nada errado com isso.
s
programa, se aceito por algum compilador, formalmente tem um comportamento imprevisível.Respostas:
O comportamento do programa é inexistente porque está malformado.
Isso é ilegal. Antes de 2011, ele estava obsoleto por 12 anos.
A linha correta é:
Fora isso, o programa está bom. Seu professor deveria beber menos uísque!
fonte
A resposta é: depende de qual padrão C ++ você está compilando. Todo o código está perfeitamente bem formado em todos os padrões ‡ com exceção desta linha:
Agora, a string literal tem tipo
const char[10]
e estamos tentando inicializar um ponteiro não const para ela. Para todos os outros tipos, exceto achar
família de literais de string, essa inicialização sempre foi ilegal. Por exemplo:No entanto, no pré-C ++ 11, para literais de string, havia uma exceção em §4.2 / 2:
Portanto, em C ++ 03, o código está perfeitamente bem (embora obsoleto) e tem um comportamento claro e previsível.
No C ++ 11, esse bloco não existe - não existe essa exceção para literais de string convertidos em
char*
, e portanto o código é tão malformado quanto oint*
exemplo que acabei de fornecer. O compilador é obrigado a emitir um diagnóstico e, idealmente, em casos como este, que são violações claras do sistema de tipo C ++, esperaríamos que um bom compilador não apenas estivesse em conformidade a este respeito (por exemplo, emitindo um aviso), mas também falhasse completamente.O código idealmente não deve ser compilado - mas sim no gcc e no clang (presumo porque provavelmente há muito código por aí que seria quebrado com pouco ganho, apesar desse tipo de falha no sistema estar obsoleto por mais de uma década). O código está malformado e, portanto, não faz sentido raciocinar sobre qual poderia ser o comportamento do código. Mas, considerando este caso específico e a história de ser permitido anteriormente, não acredito que seja um exagero irracional interpretar o código resultante como se fosse implícito
const_cast
, algo como:Com isso, o resto do programa está perfeitamente bem, já que você nunca
s
mais tocará . Ler umconst
objeto criado por meio de um não-const
ponteiro é perfeitamente normal. Escrever umconst
objeto criado por meio de tal ponteiro é um comportamento indefinido:Como não há modificação em nenhum
s
lugar em seu código, o programa funciona em C ++ 03, deve falhar ao compilar em C ++ 11, mas falha mesmo assim - e dado que os compiladores permitem, ainda não há um comportamento indefinido nele † . Com a permissão de que os compiladores ainda estão interpretando [incorretamente] as regras do C ++ 03, não vejo nada que possa levar a um comportamento "imprevisível". Nos
entanto, escreva e todas as apostas serão canceladas. Em C ++ 03 e C ++ 11.† Embora, novamente, por definição, código malformado não produza expectativa de comportamento razoável
‡ Exceto que não, veja a resposta de Matt McNabb
fonte
Outras respostas cobriram que este programa está mal formado em C ++ 11 devido à atribuição de um
const char
array a achar *
.No entanto, o programa também estava malformado antes do C ++ 11.
As
operator<<
sobrecargas estão em alta<ostream>
. O requisito paraiostream
incluirostream
foi adicionado no C ++ 11.Historicamente, a maioria das implementações
iostream
incluíaostream
mesmo assim, talvez para facilitar a implementação ou talvez para fornecer um melhor QoI.Mas seria conformar-se por
iostream
definir apenas aostream
classe sem definir asoperator<<
sobrecargas.fonte
A única coisa um pouco errada que vejo com este programa é que você não deve atribuir um literal de string a um
char
ponteiro mutável , embora isso seja geralmente aceito como uma extensão do compilador.Caso contrário, este programa parece bem definido para mim:
cout << s2
) são bem definidas.operator<<
com achar*
(ou aconst char*
).#include <iostream>
inclui<ostream>
, que por sua vez defineoperator<<(ostream&, const char*)
, então tudo parece estar no lugar.fonte
Você não pode prever o comportamento do compilador, pelas razões mencionadas acima. ( Deve falhar ao compilar, mas pode não.)
Se a compilação for bem-sucedida, o comportamento está bem definido. Você certamente pode prever o comportamento do programa.
Se não conseguir compilar, não há programa. Em uma linguagem compilada, o programa é o executável, não o código-fonte. Se você não tem um executável, não tem um programa e não pode falar sobre o comportamento de algo que não existe.
Então, eu diria que a declaração do seu professor está errada. Você não pode prever o comportamento do compilador quando confrontado com esse código, mas isso é diferente do comportamento do programa . Então, se ele vai pegar lêndeas, é melhor ter certeza de que está certo. Ou, é claro, você pode tê-lo citado incorretamente e o erro está em sua tradução do que ele disse.
fonte
Como outros notaram, o código é ilegítimo em C ++ 11, embora fosse válido em versões anteriores. Consequentemente, um compilador para C ++ 11 é necessário para emitir pelo menos um diagnóstico, mas o comportamento do compilador ou do restante do sistema de construção não é especificado além disso. Nada no padrão proibiria um compilador de sair abruptamente em resposta a um erro, deixando um arquivo objeto parcialmente escrito que um vinculador poderia pensar que era válido, resultando em um executável corrompido.
Embora um bom compilador deva sempre garantir, antes de sair, que qualquer arquivo-objeto que se espera que tenha produzido seja válido, não existente ou reconhecível como inválido, tais questões estão fora da jurisdição do Padrão. Embora tenha havido historicamente (e ainda possa haver) algumas plataformas em que uma compilação com falha pode resultar em arquivos executáveis de aparência legítima que travam de forma arbitrária quando carregados (e tive que trabalhar com sistemas onde erros de link costumavam ter esse comportamento) , Eu não diria que as consequências dos erros de sintaxe são geralmente imprevisíveis. Em um bom sistema, uma tentativa de construção geralmente produzirá um executável com o melhor esforço do compilador na geração de código ou não produzirá nenhum executável. Alguns sistemas deixarão para trás o antigo executável após uma construção com falha,
Minha preferência pessoal seria que os sistemas baseados em disco renomeassem o arquivo de saída, para permitir as raras ocasiões em que esse executável seria útil, evitando a confusão que pode resultar de acreditar erroneamente que está executando um novo código, e para a programação incorporada sistemas para permitir que um programador especifique para cada projeto um programa que deve ser carregado se um executável válido não estiver disponível com o nome normal [de preferência algo que indique com segurança a falta de um programa utilizável]. Um conjunto de ferramentas de sistemas embarcados geralmente não teria como saber o que tal programa deveria fazer, mas em muitos casos alguém escrevendo código "real" para um sistema terá acesso a algum código de teste de hardware que poderia ser facilmente adaptado ao objetivo. Não sei se vi o comportamento de renomeação, no entanto,
fonte