Links |
 |
Links de interesse ligados a programação em C++, Win32 e plataforma Microsoft em geral: |
|
| Codeproject |
| Microsoft Visual C++ |
| Visual Studio Magazine |
| C/C++ Users Journal |
| C++ FAQ Lite |
| MVP Visual C++ FAQ |
Blogs de programadores brasileiros: |
|
| Fabio Galuppo |
| Rodrigo Strauss |
| Thiago R. Adams |
|
NEXSUN FAQs |
 |
Abaixo são apresentadas as questões mais abordadas pelos alunos durante os cursos ou no desenvolvimento dos projetos, grande parte das questões aqui apresentadas estão contidas nos links citados acima, normalmente incluindo algumas informações adicionais. |
|
C++ |
 |
|
|
Q. É verdade que nem sempre um código escrito e compilado em C irá compilar em C++ ? |
Sim. Veja o exemplo abaixo:
char s[3] = "c++"; // compila em C
Essa declaração é perfeitamente legal em C, mais não irá compilar em C++. Em C, ela declara uma cadeia de caracteres de tamanho 3 e atribui 'c', '+', '+' sem o caracter terminador '\0'. Claro que essa cadeia não poderá ser usada em funções como strcpy ou printf, para tal deve-se declarar a cadeia com 4 posições. No C++ a declaração acima irá gerar um erro:
error C2117: 'c++' : array bounds overflow
Para que este tipo de construção seja válido em C++ deve-se considerar o espaço total ocupado pela cadeia considerando o terminador:
char s[4]= "c++"; // compila em C++
|
 |
Q. Qual a diferença entre declarar uma estrutura e usar um typedef ? Porquê o compilador não aceita que eu declare uma variável com o nome da estrutura que eu declarei ? |
A diferença é que a sem usar um typedef se está criando
um "tag" para a estrutura, o typedef cria um "novo tipo", sendo assim
mais abstrato. Na verdade há uma diferença entre o C e o C++ em relação a declaração de estruturas, o que confunde muitos programadores.
struct X
{ ... };
typedef struct
{ ... } X2;
X varX; // isso compila em C++ mais não em C
struct X varX; // isso compila em C++ e em C
X2 varX2; // isso compila em C++ e em C
Observe-se que no C++ typedefs são automaticamente gerados a
partir da declaração de tags de estruturas.
|
 |
Q. É possível declarar um ponteiro para uma função membro de uma classe ? |
Pode-se declarar ponteiros para funções membro de forma muito semelhante a declaração de ponteiros de funções, a única
diferença está no uso do nome da classe com o operador de escopo especificando a classe.
Para fazer a chamada deve-se usar os operadores .* e ->* O seguinte código ilustra o uso de ponteiro para uma função membro:
class ClasseA
{
public:
int Funcao1 ( int n )
{
return n;
}
};
void main()
{
ClasseA a;
int (ClasseA::* pFuncao1)(int);
pFuncao1 = ClasseA::Funcao1;
(a.*pFuncao1)( 1 );
ClasseA *pA;
pA = &a;
(pA->*pFuncao1)( 1 );
}
|
 |
Q. Como eu converto um valor decimal para hexa e vice-versa ? |
Para converter um valor decimal para sua representação em hexa, pode-se utilizar a função sprintf e utilizar "X" como especificador de formato:
sprintf( buffer, "%02X", valor );
Para se converter no sentido inverso, há várias possibilidades, uma delas é utilizar a função strtol, especificando base hexadecimal (16):
char *s="0x22";
char *erro;
long l = strtol( s, &erro, 16 );
// l irá conter 34
Outra possibilidade é utilizar a função sscanf.
|
 |
Q. Porquê se deve evitar usar funções virtuais ? |
Uma razão para se evitar funções virtuais, é o overhead gerado pelas virtual tables. Uma função não virtual é resolvida estaticamente, pois em tempo de compilação o compilador sabe qual o tipo de dado ou classe a que o objeto pertence. No caso de uma função virtual, em um ponteiro para uma classe base em que é chamado uma função virtual, não há como saber em tempo de compilação o tipo de objeto sendo utilizado, portanto não há como saber a função que deve ser efetivamente chamada na execução do código. O Visual C++ e outros compiladores utilizam virtual tables para resolver esse tipo de problema, o que nos possibilita utilizar polimorfismo. Entretanto, cada objeto que possui funções virtuais irá utilizar espaço adicional para uma virtual table, além de que as chamadas as funções virtuais serão indiretas, pois o ponteiro para a função efetivamente correspondente ao objeto estará na virtual table.
Claro que esse tipo de preocupação existe dependendo dos requisitos de cada sistema. Por exemplo sistemas de missão crítica, que alocam um grande números de objetos, e com alto requisito de escalabilidade são bons candidatos para este tipo de preocupação.
|
 |
Q. Porquê o compilador acusa erro quando eu tento usar uma função membro de uma classe como uma função de callback de uma API ? |
Esse problema acontece porquê, embora não pareça, os parâmetros esperados na função membro da classe não correspondem aos mesmos definidos na função de callback, devido ao fato de o calling convention de funções membro serem diferentes do utilizado nas funções do C++. O calling convention define entre outras coisas a forma de passagem e remoção dos parâmetros entre uma função e seu chamador, no caso de funções membros de uma classe, cada função recebe um parâmetro a mais que é o ponteiro this (no registrador ECX), o objeto sobre o qual a função deverá atuar. Esse calling convention é chamado de "thiscall". No caso das funções no C++, o calling convention default é "cdecl". Uma forma de resolver esse problema é declarar a função membro como estática. Isso funciona pois a função membro estática não recebe o ponteiro "this". Claro que há casos onde o programador quer usar uma função membro sem que seja estática; existem soluções e até bibliotecas desenvolvidas para se resolver
esse problema. Basicamente elas trabalham com a criação de funções "wrappers" para as APIs originais, que passam o ponteiro this em um dos parâmetros do callback, e o utiliza para chamar a função desejada no objeto correspondente. |
 |
Q. Ao utilizar classes do STL, eu posso acessar objetos dessas classes de forma concorrente ? As classes do STL são thread-safe ? |
Não. Ao se utilizar classes da STL em programas multithreading, é importante escrever código de proteção, garantindo que não haja acessos simultâneos. Em alguns casos pode-se utilizar um approach do tipo Single Writer Multiple Reader (também conhecido como MRSW), porém a implementação desse mecanismo é bem mais complexa do que parece inicialmente.
|
 |
Q. Há como determinar programaticamente se um computador é big-endian ou little-endian ? |
Uma forma de se fazer isso é setar o byte menos significativo em um número inteiro e verificar o valor do primeiro byte na memória:
int x = 1;
if( *(char *)&x == 1 )
cout << "little-endian";
else
cout << "big-endian";
|
 |
Q. Como eu posso criar classes que se referenciam uma a outra sem que aconteçam erros de compílação ? |
Há casos em que o programador precisa cria classes que se "conheçam" uma a outra. O problema é que, ao declarar a classe A com um ponteiro para B ou objeto do tipo B, a classe B deve ser conhecida pelo compilador. Ao compilar primeiro a classe B, caso esta também tenha uma referência para a classe A, então o problema se repete, configurando uma referência circular.
Para resolver esse problema deve-se declarar ao compilador a existência da classe, mesmo sem definí-la completamente. Isso é também chamado de "forward declaration". Exemplo:
class A
{
public:
void FuncaoA( B* b );
// Erro pois o compilador não conhece o símbolo "B"
};
class B
{
public:
FuncaoB( A *a );
// Nesse ponto o compilador já conhece o símbolo "A"
};
A classe A tem uma função membro que usa B, e B possui uma função membro que usa A. Para que o erro acima não ocorra pode-se informar ao compilador que existe uma classe B usando a declaração:
class B;
Claro que esta declaração deve estar acima da declaração da classe A.
|
 |
Q. Porquê o compilador acusa erro quando eu declaro um objeto da minha classe na forma NomeDaClasse x(); ? |
Porque as declarações seguintes são completamente diferentes:
NomeDaClasse x;
NomeDaClasse y();
A primeira linha é uma declaração de um objeto da classe NomeDaClasse.
A segunda linha é uma declaração de uma função que retorna um objeto do tipo NomeDaClasse.
Essa diferenciação ficará mais clara quando o programador tentar executar uma função membro da classe:
x.Funcao1(); // OK
y.Funcao1(); // error C2228: left of '.a' must have class/struct/union type
|
 |
Q. Por quê quando eu declaro um membro estático na minha classe eu obtenho erros de linkedição ? |
Membros estáticos devem ser definidos explicitamente, de forma semelhante a variáveis globais. Essa definição pode ser feita em qualquer módulo fonte, porém o ideal é fazê-la sempre no módulo correspondente a classe que definiu o membro estático. Exemplo:
class ClasseA
{
static int contador;
.
.
.
};
Para que o linker não acuse que o símbolo contador não foi definido, no módulo ClasseA.cpp você deve definir o membro estático, indicando o escopo da classe:
int ClasseA::contador = [valor inicial];
O [valor inicial] deve conter o inteiro correspondente ao valor inicial do membro estático.
|
 |
Q. Existe alguma forma de se criar uma função que recebe como argumento qualquer tipo de dado e converta automaticamente para string ou cadeia de caracteres ? |
Isso pode ser feito facilmente usando templates, e a classe stringstream, no exemplo abaixo foi criada um template de função para receber qualquer tipo de dado e converter para um string.
template<typename T1> static void Funcao( typename T1 p1 )
{
stringstream ss;
ss << p1;
cout << ss.str();
}
.
.
.
Funcao( 123 );
Funcao( "abc" );
|
 |
Q. O C++ criou novos operadores de cast, eu posso continuar usando o operador antigo ? Qual a vantagem de se utilizar os novos operadores ? |
O estilo antigo, "old-style" casting é obsoleto (deprecated). Desse modo, sugere-se utilizar sempre o new-style casting nos novos programas. Não há nenhuma razão para que não se utilize os novos operadores static_cast, dynamic_cast, const_cast, reinterpret_cast, uma vez que são mais específicos e com eles pode-se obter os mesmos resultados do estilo anterior, com muito maior segurança. Em resumo:
static_cast : realiza os casts mais seguros e portáveis, verificando se a conversão pode ser feita de acordo com o sistema de tipos do C++.
const_cast : basicamente utilizado para adicionar ou remover o atributo const de uma expressão.
reinterpret_cast : faz o casts de um ponteiro para qualquer outro ponteiro., ou de um ponteiro para um tipo inteiro ou vice-versa. Na verdade faz apenas uma cópia binária para o destino, sendo o mais inseguro dos operadores. Programadores ATL se deparam um grande número de utilizações desse operador.
dynamic_cast : utiliza informações em tempo de compilação e em tempo de execução para verificar se o cast pode ser realizado em um objeto, retorna NULL caso o cast seja inseguro (unsafe).
|
 |
Q. Eu posso alocar memória com new e desalocar com free, ou mesmo alocar com malloc e desalocar com delete ? Posso utilizar alocação de memória no estilo C e C++ no mesmo programa ? |
Não há nenhum problema em se utilizar new/delete e malloc/free num mesmo programa, porém jamais deve-se misturar as funções do C com os operadores do C++ em uma mesma alocação\desalocação.
Os mecanismos de alocação de memória são diferentes nos dois casos. Para sistemas compostos de vários módulos estanques, como diversas DLLs, não se pode nem mesmo alocar e desalocar memória em cópias separadas do CRT. Se o programa "host" aloca memória, e uma função de uma DLL desaloca, então deve-se tomar cuidado para que ambos utilizem a mesma cópia do CRT, o que se obtém selecionando a linkedição com a MSVCRT dinamicamente.
|
 |
Visual C++ |
 |
Q. Qual a diferença entre C++ e Visual C++ ? O Visual C++ é usado apenas para gerar programas para interfaces gráficas ? |
O C++ é uma linguagem completamente independente de sistema operacional, pode-se programar em C++ gerando programas para diversas plataformas como Windows, MSDOS, Unix, Linux, BeOS, além de um grande número de dispositivos com microprocessadores ou mesmo microcontroladores.
O Visual C++ é o compilador da Microsoft para C++. Além de aceitar o padrão C++, traz uma série de bibliotecas e componentes que auxiliam na criação de programas para a plataforma Windows, inclusive para criar programas visuais com janelas, menus, botões, etc. Estes elementos visuais não tem nada a ver com a linguagem C++ em si, embora as entidades de padronização do C++ estejam estudando a possibilidade de incorporar nos padrões futuros da linguagem bibliotecas genéricas para criação de interfaces e outros elementos comuns a grande parte dos sistemas operacionais. Estas extensões propostas tem causado grande controvérsia entre a comunidade de desenvolvedores C++...
|
 |
Q. Por quê quando eu adiciono um arquivo novo em meu projeto e faço um Build o compilador exibe a mensagem "C1010: unexpected end of file while looking for precompiled header directive." ? |
Por default os projetos criados no Visual C++ utilizam um recurso do compilador chamado "precompiled headers". Esse recurso ativado faz com que o resultado da compilação dos headers do windows seja guardado de modo que na próxima compilação não haja necessidade de recompilá-los, diminuindo em muito o tempo de compilação dos fontes. Essa informação fica quardada em um arquivo com extensão .pch, normalmente muito grande.
O que muitas vezes gera problema é que com os precompiled headers ativados o Visual C++ compila cada fonte somente a partir do ponto em que encontrar uma diretiva #include "stdafx.h" os headers contidos dentro de stdafx.h são compilados só uma vez. Se o fonte não contiver essa linha de inclusão, então o compilador acusa erro.
Embora isso possa parecer um pouco confuso funciona dessa forma. Tente fazer o seguinte teste, crie um programa no Visual C++ e adicione na primeira linha acima algo como:
lalala // isso não deveria compilar certo ? mais compila !
#include "stdafx.h"
Depois mude "lalala" para baixo do include e veja o que acontece. Você nunca mais vai esquecer como isso funciona.
#include "stdafx.h"
lalala // isso não compila
Para resolver esse problema você pode:
- Incluir #include "stdafx.h" no topo do seu arquivo fonte.
- Eliminar o uso dos headers pré compilados em: "Project / Settings / C/C++ / Precompiled Headers" escolhendo "Not using precompiled headers".
|
 |
Q. Como gerar uma listagem em assembler do meu programa no Visual C++ ? |
Basta acionar o menu Project\Settings, a tab C/C++, e em "Category" selecionar "Listing Files". A opção "Listing file type" permite definir o tipo de listagem a ser apresentado, por exemplo "Assembly, Machine Code and Source". O Visual C++ irá gerar um arquivo com extensão .COD com as instruções de máquina e source code.
|
 |
Q. O uso de funções inline sempre melhora a performance do meu programa ? |
Não. Em primeiro lugar, ao declarar uma função ou função membro com inline, não necessariamente o compilador estará efetivamente embutindo o código da função chamada sem fazer um call. O compilador irá usar uma série de critérios para determinar se fará isso mesmo ou não. No Visual C++ há um recurso adicional, a declaração __forceinline irá indicar ao compilador que o compilador deve usar a função como inline, mesmo quando os critérios do compilador acusarem uma "opinião" contrária. Mesmo com essa declaração há casos em que o compilador poderá não tratar a função como inline no chamador.
Em relação a questão de performance, o fato de a declaração inline fazer com que o código executável fique maior, pode aumentar o chamado "working set" do programa na memória, uma demanda maior de memória pode aumentar o número de page faults e por conseqüência tornar a execução do programa mais lenta.
|
 |
Q. O que significa a macro WIN32_LEAN_AND_MEAN ? |
A macro WIN32_LEAN_AND_MEAN exclui dos headers do windows APIs que não são normalmente utilizadas, por exemplo shellapi.h
Há casos de conflitos de headers, por exemplo quando se utilizam as APIs de winsock, em que a exclusão desses headers pode ser útil.
Ao contrário do que algumas pessoas imaginam, essa macro não tem nenhuma relação com a MFC.
|
 |
Q. Porquê quando eu executo meu programa sempre aparece a mensagem "No symbols found" ? |
Essa não é uma mensagem de erro, é apenas uma informação de que o compilador não achou nenhuma informação de depuração na DLL que foi carregada. Provavelmente essa DLL é uma DLL de sistema ou de terceiros e não foi desenvolvida pelo programador, então não encontrar informações de depuração é esperado. Isso pode acontecer em várias situações, por exemplo, quando o projeto de um componente lança o Internet Explorer, ou mesmo quando um projeto de uma DLL lança uma instância do Visual Basic para depuração.
|
 |
Q. Porquê o meu programa não roda em outro computador que não tem o Visual C++ instalado ? |
Quando um programa desenvolvido com Visual C++ é carregado, uma série de DLLs das quais ele depende são carregadas, de acordo com o tipo de programa foi desenvolvido e com parâmetros de linkedição do programa.
Em primeiro lugar, grande parte dos programa são dependentes de DLLs do sistema como user32.dll, advapi32.dll, etc. Uma aplicação que utiliza MFC pode ser dependente das DLLs da MFC. Além disso também pode ser dependente das DLLs de run-time (C runtime library). Outras DLLs ou componentes podem ter sido explicitamente utilizados no programa, gerando assim outras dependências. Quando o programa é carregado, se uma dessas dependências não estiver presente na máquina, então sistema operacional irá acusar um erro.
Para se determinar as dependências de um módulo ou programa, pode-se utilizar a ferramenta "Depends" que está incluída no Visual Studio, em "Start\Programs\Microsoft Visual Studio 6.0\ Microsoft Visual Studio 6.0 Tools\Depends.
Para se eliminar as dependências, pode-se alterar os parâmetros de linkedição do projeto, por exemplo usando versões estáticas das DLLs da MFC ou run-time.
|
 |
Q. Eu declarei uma estrutura com tipos de dados que ocupam um determinado número de bytes, mais o sizeof da estrutura está retornando um número maior que o esperado, isso é um erro do compilador ? |
Não é um erro do compilador, é uma característica do compilador, que ao gerar código realiza por default diversas otimizações. Uma delas é alinhar os dados na memória de modo que o acesso aos mesmos possa acontecer de forma mais eficiente. É muito mais custoso para o processador acessar dados endereçados em bytes subsequentes do que alinhados em palavras, que é a unidade de endereçamento efetivamente utilizada.
Veja o seguinte exemplo:
struct dados
{
int i;
char c;
} umDado;
Um projeto criado com as opções default do Visual C++ irá apontar 8 bytes como sizeof dessa estrutura. Programas que se comunicam através do envio e recebimento de estruturas, como programas que trabalham com sockets, devem ter especial atenção a esta questão pois se ambos os "lados" da comunicação não forem compilados com o mesmo alinhamento poderão acontecer erros nos programas.
Para alterar esse comportamento, pode-se utilizar uma diretiva #pragma pack no Visual C++, ou mesmo alterar o alinhamento das estruturas em "Project\Settings\C\C++\Code Generation\Structure member aligment", colocando-se 1 byte não haverá nenhum alinhamento por parte do compilador.
|
 |
Q. O que é a letra "L" que aparece na frente de cadeias de caracteres nos programas fonte ? Quando ela é necessária ? |
Uma cadeia de caracteres que inicia com L é tratada pelo compilador como uma cadeia de caracteres UNICODE, onde cada caracter ocupa 2 bytes. Isso independe de se estar gerando um projeto UNICODE ou não, diferente da macro "_T", que é uma macro condicional, cujo resultado depende da definição do símbolo UNICODE no projeto, podendo resultar em uma cadeia ANSI ou UNICODE. Exemplo: L"Linguagem C++" sempre resulta em uma cadeia UNICODE.
Há APIs que sempre recebem cadeias UNICODE, por exemplo SysAllocString, sendo um caso típico da utilização de strings literais com "L".
|
 |
Win32 |
 |
Q. É preciso instalar o "Win32 Platform SDK" para se desenvolver com a Win32 ? |
Quando se instala o Visual C++, automaticamente se instala um subset da "Win32 Platform SDK". A Microsoft atualiza freqüentemente o SDK, incluindo novos recursos de modo que através de um CD da Microsoft ou download é que se terá a versão mais atual e completa.
Muitas das bibliotecas do SDK são bibliotecas avançadas, como novas APIs ou APIs mais hardcore como desenvolvimento de device drivers, na grande maioria das vezes a versão liberada com o Visual C++ é suficiente para aplicações comuns.
|
 |
Q. Como eu executo um programa externo de dentro do meu programa ? |
Há várias formas de se fazer isso, usando diferentes APIs, e a escolha depende do grau de controle que se precisa ter sobre o programa sendo executado. Uma possibilidade é usar WinExec:
WinExec ( "C:\\WinNT\\System32\\Notepad.exe", SW_SHOWNORMAL );
Usando ShellExecute pode-se ter mais controle, podendo especificar uma janela pai ou mesmo executar operações sobre um arquivo:
ShellExecute ( hwndJanelaPai, "open", "C:\\dados.txt", NULL, NULL, SW_SHOWNORMAL );
Outra opção é usar a API CreateProcess, que possibilita maior controle sobre o processo sendo lançado:
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi = {0};
BOOL sucesso;
sucesso = CreateProcess ( NULL, "\"C:\\Program Files\\Microsoft Visual Studio\\VB98\\VB6.EXE\"", NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi );
CloseHandle ( pi.hThread );
CloseHandle ( pi.hProcess );
|
 |
Q. Como se restringe que uma aplicação tenha apenas uma instância em execução ? |
Para se garantir apenas uma instância de uma aplicação ativa no sistema operacional, normalmente se cria um objeto no kernel do sistema operacional que seja visível por qualquer instância da aplicação. No início da aplicação pode-se verificar se esse objeto já existe, o que indica que já existe uma instância ativa, se não existir então deve ser criado sinalizando uma instância da aplicação ativa. Dentre esses objetos pode-se citar eventos, semáforos ou mesmo mutexes, que são utilizados para sincronismo entre processos, portanto são visíveis entre aplicações. Para tal a criação desse objeto deve ser feita a partir de um nome conhecido e único. Há programadores que usam nomes do tipo XYZ... ou algo semelhante. Uma forma mais elegante é criar um GUID com uma ferramenta como o guidgen.exe, garantindo efetivamente um nome único. Um exemplo dessa técnica usando-se um mutex é dada abaixo.
int WINAPI WinMain(...)
{
HANDLE hObjeto = CreateMutex( NULL, FALSE,"{CC0918E0-EFE5-11CF-A044-00AA00B6015C}");
if ( GetLastError() == ERROR_ALREADY_EXISTS )
return FALSE;
}
int Finalizar()
{
// ...
CloseHandle( hObjeto );
}
|
 |
Q. Como eu posso acessar diretamente setores do disco usando Win32 ? |
Através das APIs CreateFile, ReadFile, WriteFile, pode-se acessar diretamente setores dos discos rígidos e flexíveis para operações de leitura e escrita. Estas APIs podem ser usadas com esse fim não só no Windows 2000/XP, mas também no Windows 98.
|
 |
Q. Como eu determino a versão do Windows em que meu programa está sendo executado ? |
Para determinar a versão do Windows pode-se utilizar a API GetVersionEx:
BOOL GetVersionEx( LPOSVERSIONINFO lpVersionInfo );
O exemplo abaixo, extraído do MSDN, verifica se o sistema operacional é o Windows XP ou posterior:
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx (&osvi);
bIsWindowsXPorLater =
( (osvi.dwMajorVersion > 5) ||
( (osvi.dwMajorVersion == 5) && (osvi.dwMinorVersion >= 1) );
|
 |
Q. Eu escrevi um serviço NT, existe alguma API específica para que um programa se comunique com o serviço ? Qual a melhor forma de comunicação inter-processos ? |
Não existe uma API específica para comunicação com serviços.
As mesmas APIs Win32 que podem ser usadas para comunicar programas podem ser usadas para comunicação com serviços.
Entre essas APIs pode-se citar: sockets, mailslots, pipes, RPC, COM\DCOM, mensagens, etc. Cada API apresenta um conjunto de características, como possibilidade de comunicar em máquinas diferentes ou em ambiente Internet, velocidade, facilidade e outras diferenças, portanto não existe uma regra geral.
|
 |
Q. Como eu posso detectar que o usuário da máquina está ausente ou "idle" ? |
A Win32 possui a função GetLastInputInfo, que informa o tempo em que ocorreu o último evento de entrada:
BOOL GetLastInputInfo( PLASTINPUTINFO plii );
A estrutura do tipo LASTINPUTINFO irá devolver em dwTime o tempo do último evento.
|