# 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.

Comentários

  • [3/5/06 08:51] Daniel Quadros:Isto é um teste: demora para aparecer o comentário ou o site comeu o comentário que eu fiz?
  • [3/5/06 08:56] Daniel Quadros:Comeu de novo... Como eu coloco código em um comentário?
  • [3/5/06 11:48] Wanderley Caloni Jr:Testando (também).
  • [3/5/06 11:52] Wanderley Caloni Jr:Pois é, acho que seu código não apareceu. Mas não deveria ter problemas, só que o blogger é feito em software, e nunca se sabe. Pode mandar o seu comentário com código por e-mail? wanderley@caloni.com.br.
  • [3/5/06 13:54] Wanderley Caloni Jr:Caloni,

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

  • [5/5/06 13:40] Kabloc:Quando eu estava fazendo este código eu estava brincando com o pessoal da faculdade:É que o professor passou a difícil tarefa "Escrever um três código que imprime a tabuada do '7', um com 'while', um com 'do while' e outro com 'for'", aí eu queria imprimir todas as tabuadas com um fonte do mesmo tamanho, e o mais confuso possível.

Tudo não passou de uma brincadeira...

  • [5/5/06 14:00] Wanderley Caloni Jr:Pois é, deu pra perceber que seu código não era de "produção" =).

# ++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 =).

  • [16/5/06 12:08] Pedro:Feliz Aniversário. Ando frequentando o site do Strauss desde do anuncio do primeiro encontro de Programadores C++. E lá, em algum link, assim despercebido, acabei incluindo seu blog na minha lista de RSSs....Não pude ir nos dois encontros, vontade não faltou.for(int i = 0; i < 1000; i++) std::cout < < "Parabens!" < < std::endl;Saudações C++!
  • [16/5/06 12:28] Kabloc:Feliz aniversário Blog do Wanderley, eu sei o quanto você adora receber um Parabéns Wanderley por isso resolvi comentar, Feliz aniversário Blog do Wanderley !!!

Heheheheeheh !!!!

Um abraço amigão que seu blog lhe-traga muito sucesso !!!

  • [16/5/06 15:17] Daniel:// loop sem condição de saídafor (;;)printf ("Parabens!\n");
  • [17/5/06 12:11] Luciano:Eu sempre leio seu blog mesmo sem comentar. Graças ao pessoal que tá movimentando o C++ no Brasil comecei a estudar essa linguagem. Estou me convertendo ao lado unmanaged da força. Parabéns pelo ótimo conteúdo!!A propósito, a joaninha com a vela ficou ótima :D.
  • [17/5/06 13:13] Wanderley Caloni Jr:Eheheheheheh valeu. Valeu a todos. É bom saber que o movimento está "aceso" e que mais pessoas estão se interessando.

[]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.

Fechamento duplo de templates

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).

For Each válido para STL

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.

Macros com número de parâmetros variáveis

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".

O CRT da Microsoft

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.

O que você não conhece não pode feri-lo

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):

  • trocar os "vcprojs" por makefiles e usar as libs do SDK (razoável, mas dá trabalho),
  • manter uma solução separada para compilar uma versão só para o 95/NT (isso dá trabalho e é triste),
  • usar o ambiente do VC8 mas o compilador, as bibliotecas e os headers do 7.1 (isso dá trabalho e é nojento),
  • depois de compilado, alterar a IAT do binário para retirar a dependência da função... (pára! pára! alerta vermelho!)

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:

  • A CRT tem um símbolo pendente com o nome "__imp__IsDebuggerPresent@0", que significa "função exportada por uma DLL com seu nome disponível em uma LIB de interface com o nome IsDebuggerPresent, usando a convenção de chamada __stdcall e que não recebe nenhum parâmetro".
  • Nós criamos uma função em nosso cpp com o nome "_imp__IsDebuggerPresent" que não recebe nenhum parâmetro e com a convenção de chamada __stdcall. O compilador coloca um underline na frente do nome (ele faz isso com todos os nomes) e, como a convenção de chamada manda, o caractere "@" seguido do número de bytes que os parâmetros da função ocupam na pilha (0 parâmetros * 4 bytes cada um = 0 bytes).
  • O link começa e o linker tem uma regra de ouro: os símbolos encontrados dentro dos próprios objs tem precedência sobre os símbolos que irei encontrar nas LIBs.
  • Ora, não é que o linker achou o símbolo que a CRT precisa já no nosso obj? Que coincidência!
  • Esse símbolo será usado no lugar de outros símbolos eventualmente encontrados em outros lugares, como a kernel32.lib, por exemplo.
  • Como esse símbolo é interno, não existe mais a dependência da função kernel32!IsDebuggerPresent. Como não existe, o executável gerado roda normalmente em um Windows 95, pois ele já não mais depende de uma função que não existe no sistema operacional.

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.

Comentários

  • [27/5/06 11:15] Anderson Moreira:Sensacional :D
  • [28/5/06 16:01] Thiago Adams:Na opção "Runtime Library" vocês estavão usando "Multi-threaded (/MT)" ou "Multi-threaded DLL (/MD)"?Não sei se faz diferença.. mas gostaria de saber se usando "Multi-threaded (/MT)" também não funciona no 95?
  • [29/5/06 09:49] Wanderley Caloni Jr:Sim, Thiago. Em ambas as configurações não roda no 95 por conta da função IsDebuggerPresent. Se você usar a gamb... adaptação técnica que mostrei acima, ele roda, mas de qualquer forma a Microsoft não suporta.[]s

[next: 2006-04] [prev: 2007-06]