# Diferenças entre C e C++: precedência!
Caloni, 2006-05-03 <computer> <veryold> <draft> [up] [copy]Num belo dia, meu amigo Kabloc escreveu uma pequena e singela função para imprimir a tabuada de 1 a 10. O código era mais ou menos assim:
#include <stdio.h>
int main()
{
int f1,f2,s=0;
for(f1=1;(f1==11&&s!=5)?s=5,f1=0,putchar(10):(f1<=10)?f1=f1:f1=12,f1<=11;f1++)
for(f2=1+s;f2<=5+s;f2++)printf("%dx%d=%d%c",f2,f1,f1*f2,(f2==5+s)?10:9);
return 0;
}
Tirando o fato que o fonte é candidato forte ao "The International Obfuscated C Code Contest", o pessoal do linux disse para ele que o dito cujo não compilava no GCC, e que em algum lugar naquelas 4 linhas dentro do main alguma coisa não era padrão da linguagem.
Procurado para resolver o problema, dado minha inata vocação para assuntos aleatórios, resolvi resolver as coisas no bom e velho Visual Studio 2003. Afinal de contas, ele compila fontes em C, também. Basta alterarmos a extensão de um fonte de .cpp para .c. E foi o que eu fiz. Foi assim que encontrei o seguinte erro de compilação, que não ocorre ao compilar o mesmo fonte em um arquivo C++:
error C2106: '=' : left operand must be l-value
Isso acontece na linha 6, a linha do primeiro for. E eis um fonte em C que compila em C++, mas dá erro de falta de l-value quando compilado como C puro.
Pensando um pouco sobre o problema, mais de maneira intuitiva do que propriamente destrinchando e analisando a linha de código, imaginei que poderia haver alguma diferença minúscula entre a tabela de precedência de operadores dessas duas linguagens. Tendo isso em mente, saquei os padrões das respectivas linguagens da minha pasta de e-books e, vejam só:
c c++ Operators Associativity Operators Associativity () [] -> . left to right () [] -> . etc ! ~ ++ -- etc right to left ! ~ ++ -- etc right to left * / % left to right * / % left to right + - left to right + - left to right << >> left to right << >> left to right < <= > >= left to right < <= > >= left to right == != left to right == != left to right & left to right & left to right ^ left to right ^ left to right | left to right | left to right && left to right && left to right || left to right || left to right ?: right to left = += -= *= /= %= etc right to left = += -= *= /= %= etc right to left ?: left to right , left to right , left to right
Nota: Tabela C++ reduzida; padrão ANSI da linguagem C de 1989.
Fora alguns detalhes agora menos importantes, nota-se que no final da tabela existe uma inversão na precedência entre o operador condicional ternário e os operadores de atribuição; isso sem contar que a ordem de avaliação também muda. Em C, o operador ternário é avaliado da direita pra esquerda, enquanto em C++ da esquerda pra direita, aliás como a maioria. Isso vai fazer com que, na linha 6, a mesma expressão tenha diferentes interpretações para cada linguagem.
Para entender passo a passo o problema, vamos destrinchar a segunda parte do laço for da linha 6:
( f1 == 11 && s != 5 ) ? s = 5, f1 = 0, putchar(10) : ( f1 <= 10 ) ? f1 = f1 : f1 = 12, f1 <= 11;
Nós temos dois operadores ternários aninhados. De acordo com o padrão C++, os operadores ternário têm precedência menor que os operadores de atribuição, e são avaliados da esquerda pra direita. Ou seja, primeiro são avaliadas todas as atribuições da expressão antes de qualquer operador ternário. Depois disso, o primeiro operador ternário é avaliado, seguido do segundo:
( ( f1 == 11 && s != 5 ) ? (s = 5), (f1 = 0), putchar(10) : ( f1 <= 10 ) ) ? (f1 = f1) : (f1 = 12), f1 <= 11;
As partes em vermelho são as primeiras a serem avaliadas, depois as em verde e, por fim, as em azul. Essa prioridade de cores é totalmente arbitrária. As cores que você vê no seu editor de textos não tem nada a ver com essa explicação =).
Agora analisemos em C. Nessa linguagem, ao contrário de C++, os operadores ternários possuem precedência maior que os operadores de atribuição, e são avaliados da direita para a esquerda. Isso quer dizer que primeiro o último operador ternário é avaliado, ignorando a atribuição mais à direita, e depois o primeiro operador ternário é avaliado. Só depois disso que a atribuição mais à direita é feita:
( ( f1 == 11 && s != 5 ) ? s = 5, f1 = 0, putchar(10) : ( ( f1 <= 10 ) ? f1 = f1 : f1 ) ) = 12, f1 <= 11;
Tudo isso para compreendermos que a atribuição do valor 12 será feita em cima do resultado do primeiro operador ternário, cujos valores possíveis poderão ser ou o retorno de putchar ou f1. Lembre-se de que o operador vírgula serve para encadear expressões, avaliá-las, e retornar o valor da expressão mais à direita:
s = 5, f1 = 0, putchar(10) // atribui 5 a s, 0 a f1 e retorna o valor da chamada a putchar. f1 = f1 : f1 // em ambos os casos o valor retornado é a variável f1.
Ora, a variável f1 é do tipo int. E putchar também retorna um int. Isso não quebra nenhuma regra de tipagem. Porém, quebra a regra de ouro da atribuição, que possui um lugar de destaque nas escrituras sagradas: "colocarás um lvalue ao lado direito de uma atribuição; não serás um 10, muito menos uma constante, mas uma variável; e assim foi feito!".
f1 = 12; // certo; nada a declarar (exceto o f1) putchar(10) = 12; // opa! que que é isso? putchar não retorna variável, e sim uma constante
Isso termina a longa explicação do porquê aquele errinho lá no começo do artigo ocorreu apenas na linguagem C. Esse é um exemplo das pequenas diferenças entre essas duas linguagens. Provavelmente se você usa parênteses feito um doente mental não encontrará problemas desse tipo em seus códigos. Lembrando que isso não desmerece o operador ternário, tão útil e econômica expressão dessas duas linguagens que primam pela elegância e exatidão.
Segue abaixo o comentário que o blog comeu : (
Felizmente eu costumo usar parenteses ao invés de depender da precedênciados operadores. Por exemplo, eu escreveria ((f1==11)&&(s!=5)). Além disso,já passei da fase de economizar linhas e colunas no fonte. A minha versãopara a tabuada (não reproduzindo a esquisitice do fator 0 na segunda metade)seria:
#include < stdio.h >
int main()
{
int f1,f2,s=0;
for (s = 0; s < 10; s += 5)
{
for (f1 = 1; f1 <= 10; f1++)
{
for (f2 = 1+s; f2 <= (5+s); f2++)
printf (\"%dx%d=%d%c\", f2, f1, f1*f2, (f2==5+s) ? \'\\n\': \'\\t\');
}
if (s != 5)
putchar(\'\\n\');
}
return 0;
}
Abraços,
Daniel Quadros
Tudo não passou de uma brincadeira...
# ++Blog: um ano de caloni.com.br!
Caloni, 2006-05-15 <computer> <veryold> <draft> [up] [copy]Bom, o nascimento começou mais ou menos assim: meu velho amigo Strauss deu um pequeno apoio moral ("escreve! escreve! escreve!"), o primeiro comentário no embrião que viria a ser algo parecido com um blog. Na época, Strauss já mantinha um site de respeito sobre C++ (entre outras coisas). Lembro-me que até depois desse seu primeiro comentário, a preguiça ainda imperava sobre mim na difícil arte de escrever mal.
Porém, quinze dias depois, eis que surge o primeiro artigo. Praticamente um aborto. Nele já podemos perceber todo o poder de filosofar indo pra lugar algum: uma discussão infrutífera sobre o conceito de implementação do controle condicional de fluxo de um código de máquina com um exemplo em algo nível. Dá-lhe, Wanderley! Ficou legal essa estréia =).
Depois disso, aos poucos - e põe pouco nisso - os artigos foram saindo um a um. E entre técnicas de injeção de código e curiosidades inúteis de C++, cheguei ao ponto de, com um pouco de receio, confesso, escrever alguma coisa sobre aquele ambiente que poucos conhecem e confundem com o Delphi: o C++ Builder.
O tempo passou e os posts continuaram. O contador de visitas continuava zerado, como deveria ser (acho que meu público-alvo era eu mesmo). Daí, mais ou menos no final do ano passado, aconteceu: estávamos formando o início de uma comunidade C++. Houve o "Primeiro Encontro de Programadores C++" (chique, não?) e, três meses depois, o segundo (imperdível). A partir daquele momento já não eram apenas dois ou três participantes do seleto grupo de programadores de unmanaged code aqui no Brasil. Se tratavam de quatro, cinco, seis... talvez até sete (não sei se estou chutando alto). Isso não é o máximo??
Bom, pra mim isso foi o máximo, pois permitiu que tivéssemos hoje o nosso site wiki e o nosso fórum de discussões. Para a nossa pequena turma sem representação aqui no Brasil - representação não comercial - isso já foi uma conquista. É um grande motivo para eu continuar escrevendo meus textos não-tão-bons e meu código de caráter duvidável. E por isso continuo.
É isso aí. Nem dá pra acreditar. 365 dias, 12 meses, 1 ano e 45 posts (e contando). É a primeira velinha que eu acendo. E eu mesmo apago. Fazendo votos de que esse loop continue sem condição de saída =).
Heheheheeheh !!!!
Um abraço amigão que seu blog lhe-traga muito sucesso !!!
[]s
# O que muda em C++ no Visual Studio 2005
Caloni, 2006-05-22 <computer> <veryold> <draft> [up] [copy]Confesso que não estava confiante, nem animado, em usar essa nova versão. Mas, enfim, o dia chegou. Essa semana instalei na minha máquina do serviço para o início da migração de nossos projetos. O processo de conversão ficou por conta de um outro colega, porém todos os problemas eram imediatamente reportados a mim (como se eu fosse o culpado ou algo do tipo pelos caprichos da equipe do VC8). Pra resumir desde o início, a visão geral que tive foi que a nova versão aumenta a compatibilidade com o C++ ISO, porém fornece novas extensões e erra em algumas mudanças que, como explico mais adiante, comprometem o aprendizado do estudante e um dos objetivos do C++.
Observação introdutória: note que todas as minhas observações sobre as mudanças na linguagem dizem respeito ao comportamento padrão do ambiente, do jeito que está quando você instala. Mesmo que haja opções de compilação para alterar a interpretação de alguma construção da linguagem, elas não foram levadas em consideração nem foram pesquisadas a fundo. Tenho dito.
Primeiro, as boas (!?) novas. Lembram daquele conhecido problema que temos ao usar um template como parâmetro de outro? Refrescando a memória:
#include <vector>
template<typename T>
class MyTempl
{
T m_x;
};
int main()
{
std::vector< MyTempl< int >> x; // ops, shift ou dois fecha-template?
std::vector <MyTempl< int > > y; // assim, sim
}
Esse problema ocorre na linguagem porque um template pode receber além de um tipo uma expressão inteira constante, inclusive com operadores aritméticos, tais como mais, menos, dividir, multiplicar, e o shift:
#include <vector>
template<int T>
class MyTempl
{
enum { T };
};
int main()
{
// válido na linguagem. não no 2005
std::vector<MyTempl< 2 >> 4 > > x;
std::vector<MyTempl< ( 2 >> 4 ) >> x; // válido no 2005, não na linguagem
}
Erros da primeira linha:
error C2143: syntax error : missing ';' before 'constant' error C2059: syntax error : '>'
Muito razoável e prática a "correção" que o pessoal faz na linguagem (tomara que seja feita no C++0x), pois é evento raro usarmos shift dentro de um parâmetro de template, e, se necessário, temos como corrigir usando parênteses (o mesmo vale para o operador de comparação menor que). Só que o código que não usa espaço no fechamento de dois templates consecutivos não é C++ padrão. Eu pessoalmente prefiro as coisas como elas são, pois já fiquei ligeiramente irritado por conta de uma outra facilidade do ambiente. O pior é que nada disso está especificado no help (procurei na lista de novidades da linguagem).
Essa é pra dar dó de não usar. Vejam o exemplo da própria Microsoft:
// for_each_stl.cpp
// compile with: /EHsc
#include <map>
#include <iostream>
using namespace std;
int main() {
int retval = 0;
map<const char*, int> months;
months["january"] = 31;
months["february"] = 28;
months["march"] = 31;
months["april"] = 30;
months["may"] = 31;
months["june"] = 30;
months["july"] = 31;
months["august"] = 31;
months["september"] = 30;
months["october"] = 31;
months["november"] = 30;
months["december"] = 31;
map<const char*, int> months_30;
for each( pair<const char*, int> c in months )
if ( c.second == 30 )
months_30[c.first] = c.second;
for each( pair<const char*, int> c in months_30 )
retval++;
cout << "Months with 30 days = " << retval << endl;
}
Não posso negar que eu adoro o conceito do for each. A STL já implementa um no header algorithm que se utiliza de objetos-funções. Mas, cá entre nós, o uso do código dentro da própria função é show de bola. Essa é mais uma feature não-ISO para seduzir o povo. Espero não cair na tentação.
Ah, sim! Finalmente temos a muito da útil implementação de uma das inovações do padrão da linguagem C em 99. Esperava isso só pra trocar minha implementação desajeitada de chamada de trace:
#include <stdio.h>
#ifdef _DEBUG
#define LOG(args) log args
#else
#define LOG(args)
#endif
void log(const char* fmt, ...)
{
va_list vList;
// bla bla bla
vprintf(fmt, vList);
// bla bla bla
}
int main(int argc, char* argv[])
{
LOG(("main: argc = %d.\n", argc)); // tenho que usar duplo parênteses
}
Pela muy graciosa forma inventada/aceita pelo pessoal do C ISO:
#include <stdio.h>
#ifdef _DEBUG
#define LOG(fmt, ...) printf(fmt, __VA_ARGS__)
#else
#define LOG(fmt, ...)
#endif
int main(int argc, char* argv[])
{
LOG("main: argc = %d.\n", argc); // agora um par de parênteses só
}
Bom, talvez eu espere isso fazer parte do C++ também. Por enquanto, só seria válido em programas puramente feitos em C.
Fora isso, pequenos detalhes em código específico foram modificados nessa versão para que o compilador estivesse em maior conformidade com as firulas do padrão. Se trata de código tão específico que me abstenho de falar sobre isso em um artigo que já vai ficar grande. Mas aos fanáticos interessados recomendo a leitura. Existe uma lista particularmente boa em "Breaking Changes in the Visual C++ 2005 Compiler".
Bom, depois dessa lista imensa de mudanças não-portáveis e excitantes que o pessoal do Visual C++ fez com o compilador, nada como criticar duas mudanças que considero uma atitude prepotente e desavisada. A primeira delas diz respeito à famigerada sinalização de deprecated em todas as funções da bibioteca C que utilizam strings.
É ruim pra mim? Claro que não. Já apanhei o que devia para aceitar com naturalidade os erros e warnings que ocorrem durante uma compilação em C. Mas é uma falta de consideração para aquela galera que está começando a fazer seus primeiros int mains e se apavora se esqueceu um ponto e vírgula. Sem contar que as clássicas funções da linguagem C receberam a inglória denominação de funções deprecated, junto com aquelas funções que não serão suportadas nas futuras versões do compilador. Isso tem uma força pejorativa ainda maior que aquela história do unmanaged code. Se você aperta F1 sobre o warning recebe, entre outras coisas, a seguinte informação:
"The compiler encountered a function that was marked with deprecated. The function may no longer be supported in a future release. You can turn this warning off with the warning pragma (example below)."
Só na eventualidade de clicarmos no link Security Enhancements (?) in the CRT é que passamos a entender o que está acontecendo:
"It should be noted that in this context, "deprecated" just means that a function's use is not recommended; it does not indicate that the function is scheduled to be removed from the CRT."
Ah, tá! Deprecated quer dizer uma coisa, mas no caso específico da CRT quer dizer outra. Agora, sim.
Pra finalizar, a máxima de C++. A turma do padrão fez de tudo para fazer essa regra valer por toda a implementação da linguagem. Se seu código não lança ou trata exceções, não existirá sobrecarga para suportar isso. Se sua classe não tem métodos virtuais, não existirá uma vtable para ela. Se não usa RTTI (com o uso de typeid ou dynamic_cast), o suporte a ela não será incluído. Nada que você não queira explicitamente que cause sobrecarga em tempo de execução será incluído "por baixo dos panos" no binário final.
Agora, olhe o seguinte código:
#include <windows.h>
/** Vamos fazer um exemplo convincente de uso da linguagem e não-uso da
biblioteca C++.
*/
int main(int argc, char* argv[])
{
if( argc == 2 )
{
STARTUPINFOA si = { sizeof(si) }; // inicia primeiro membro e zera resto
PROCESS_INFORMATION pi;
CreateProcessA(NULL, argv[1], NULL, NULL, TRUE, 0,
NULL, NULL, &si, &pi);
}
}
Uso esse exemplo porque foi em um código parecido com esse que encontrei "a arte" que fizeram com a inicialização de structs. Se tratava de um projeto onde, por questões de espaço, evitamos usar a biblioteca C++ e qualquer feature que dela precisasse. Isso compilava e linkava perfeitamente no VC 2003. Qual não foi nossa surpresa ao descobrir que, debaixo dos panos, alguma coisa estava sendo chamada:
; 11 : STARTUPINFOA si = { sizeof(si) }; // inicia primeiro membro e zera resto
0000a 6a 40 push 64 ; 00000040H
0000c 8d 44 24 18 lea eax, DWORD PTR _si$58933[esp+92]
00010 6a 00 push 0
00012 50 push eax
00013 c7 44 24 1c 44
00 00 00 mov DWORD PTR _si$58933[esp+96], 68 ; 00000044H
0001b e8 00 00 00 00 call _memset ; obs: fala sério!
Uma chamada ao memset. Em Debug e em Release. Precisa dizer mais? Simplesmente tivemos que voltar a linkar com a LIBC porque alguém decidiu que a chamada inline ao memset ou de um simples opcode em assembly por algum motivo seria pior do que depender da LIBC. Gostaria de saber o que pensa o Matt Pietrek sobre isso, ele que desenvolveu uma LIBC mais enxuta só pra não ter que linkar com código que ele simplesmente não utiliza. Uma visão pessimista diria que a Microsoft já não está muito aí pro bom e velho C++ que prima pela economia porque agora, afinal de contas, eles têm o C++ deles. Uma visão otimista diria que esse é apenas um dos novos bugs que toda versão nova de software costuma ter, e que futuramente será corrigido. Espero que dessa vez os otimistas estejam certos.
# VC++ 2005 e Windows 95 não casam (atualizado)
Caloni, 2006-05-26 <computer> <draft> <veryold> [up] [copy]Hoje foi mais um dia de surpresas com a nova versão do ambiente de programação da Microsoft. De cara descobrimos que programas que usam a nova CRT não rodam no Windows 95. Eu quero dizer: não é um projeto específico; QUALQUER projeto não irá rodar. Tentamos executar um projeto vazio e existe uma dependência da função IsDebuggerPresent nos executáveis gerados. Essa função API inexiste nessa versão do SO.
Obs: solução não funciona na execução. Veja detalhes no final do artigo.
Bem, esse é o tipo de coisa que pára o desenvolvimento. De repente temos um problema que pode comprometer todo o processo de migração. Uma pesquisa inicial no google e google groups não revelou muita coisa, só uma discussão em alemão sem links interessantes. Mas, alguns minutos de fuçação e busca depois e achamos este link, que documenta a tragédia: no Visual C++ 2005, nem o Windows 95 nem o Windows NT 4 são suportados.
O que é muito justo, sabendo que a Microsoft encerrou o suporte técnico destes dois sistemas já faz algum tempo. Mas, cá entre nós, não fazer rodar no 95 só por causa do IsDebuggerPresent parece uma limitação desnecessária. Tudo bem que não é mais suportado (nós mesmos programadores já não suportamos mais esse sistema) e que talvez muito mais coisa não funcione. Mas, diabos, compile e deixe rodar. Afinal, a estabilidade já não é um forte nem do sistema operacional. O hipotético usuário de Windows 95 no século XXI não iria nem esquentar muito se um de seus programas compilados em VC8 eventualmente explodisse de vez em quando. Ele de cara já iria botar a culpa no Windows e apertar o botão de reset. O instinto Microsoft. Lembram?
Mas, voltando ao tema do artigo:
// gs_report.c
__declspec(noreturn) void __cdecl __report_gsfailure(void)
{
/*
...
muito código
..
*/
#if defined (_CRTBLD) && !defined (_SYSCRT)
DebuggerWasPresent = IsDebuggerPresent(); // AQUI!! MALEDETO!!
_CRT_DEBUGGER_HOOK(_CRT_DEBUGGER_GSFAILURE);
#endif /* defined (_CRTBLD) && !defined (_SYSCRT) */
/*
...
mais código
..
*/
}
// enquanto isso, no invarg.c ...
_CRTIMP void __cdecl _invoke_watson(
const wchar_t *pszExpression,
const wchar_t *pszFunction,
const wchar_t *pszFile,
unsigned int nLine,
uintptr_t pReserved
)
{
/*
...
lá lá lá...
..
*/
ExceptionPointers.ExceptionRecord = &ExceptionRecord;
ExceptionPointers.ContextRecord = &ContextRecord;
wasDebuggerPresent = IsDebuggerPresent(); // AH, MUUUULEEEEKEEE!!!
/* Make sure any filter already in place is deleted. */
SetUnhandledExceptionFilter(NULL);
/*
...
mais código
..
*/
}
Essas duas funções de tratamento de erros e exceções presentes nos fontes da biblioteca C é que estão zoando o barraco. Nesse caso, saber onde está o problema não resolve muito. Temos os fontes, podemos alterá-los. Mas isso não significa que podemos usar uma nova CRT modificada e compilada por nós. Isso seria impor uma dependência muito severa na portabilidade do projeto. Ele mal compilaria fora daquela sala!
Como modificar os fontes da Microsoft é uma gambi acima dos níveis a que estávamos dispostos a aceitar naquele momento (era de manhã ainda), resolvemos que as coisas precisariam ficar mais feias para chegarmos a esse ponto. Algumas alternativas sugeridas foram (por ordem de desespero):
Até que veio a solução mágica (tudo bem, não tão mágica; mas boa o suficiente para ser implementada):
//
//solucaonaotaomagicamasboaosuficienteparaserimplementada.cpp
//
#include <windows.h>
BOOL
WINAPI
_imp__IsDebuggerPresent(VOID)
{
BOOL ret = FALSE;
// aqui podemos chamar a função de verdade, mas
// sem usar link estático. e rodar no 95 =)
return ret;
}
Uau! Isso que eu chamo de sobrecarga de função "na marra". Se nossas mães vissem isso (e entendessem isso) iriam ficar orgulhosas.
Vamos entender o que acontece durante o processo de link:
Esse é o fim da história. Com isso conseguimos enganar o linker. Ele encontra nosso símbolo antes da kernel32.lib e usa ele no lugar. E com isso resolvemos mais um dos problemas que ocorre nos processos de migração de versão dos ambientes Microsoft. Que tal?
PS: com essa pequena "aramação" foi corrigido o problema de dependência, porém não o de execução. O programa roda quando não chamadas as funções sobrepostas. Aliás, é interessante notar que a CRT do vc referencia a função, mas acaba não chamando em uma compilação default, o que conseqüentemente aumenta o nível de revolta. Estou analisando o problema agora e, caso ache uma solução simples e plausível, colocarei em um futuro artigo. No momento peço desculpas pela publicação precipitada de uma solução não testada.