Por que os ponteiros não são recomendados ao codificar com C ++?


45

Li em algum lugar que, ao usar C ++, é recomendável não usar ponteiros. Por que os ponteiros são uma péssima idéia quando você está usando C ++. Para programadores C que estão acostumados a usar ponteiros, qual é a melhor alternativa e abordagem em C ++?


40
faça o link para "algum lugar". O contexto pode ser muito relevante.

1
Espero que esta pergunta seja útil para você.
Garet Claborn

A maioria dessas respostas refere-se a evitar vazamentos de memória como o principal motivo. Não me lembro da última vez em que um de nossos aplicativos teve um problema de vazamento de memória, apesar de usar ponteiros. Se você tiver problemas de vazamento de memória, não está usando as ferramentas certas ou não sabe o que está fazendo. A maioria dos ambientes de desenvolvimento tem uma maneira de verificar automaticamente os vazamentos incorporados. Acho que os problemas de vazamento de memória são muito mais difíceis de rastrear nos idiomas coletados pelo lixo, porque a ocorrência deles é muito mais sutil e você frequentemente precisa de uma ferramenta de terceiros para rastrear o culpado .
Dunk

1
Adicionando ao comentário do @Dunk, às vezes coletores de lixo embutidos em idiomas de nível superior simplesmente não funcionam corretamente. O coletor de lixo do ActionScript 3 não funciona, por exemplo. No momento, há um bug relacionado a NetConnectioninstâncias desconectadas do servidor ( stackoverflow.com/questions/14780456/… ), além de um problema de haver vários objetos em um programa que ele se recusará especificamente a coletar. ...
Panzercrisis

... ( adobe.com/devnet/actionscript/learning/as3-fundamentals/… - procure GCRoots are never garbage collected.e o parágrafo iniciado por The MMgc is considered a conservative collector for mark/sweep.). Tecnicamente, esse é um problema no Adobe Virtual Machine 2, não no AS3, mas quando você tem problemas como esse em idiomas de nível superior, com coleta de lixo essencialmente incorporada, geralmente não há uma maneira verdadeira de depurar o idioma. essas questões completamente fora do programa. ...
Panzercrisis

Respostas:


58

Eu acho que eles significam que você deve usar ponteiros inteligentes em vez de ponteiros regulares.

Na ciência da computação, um ponteiro inteligente é um tipo de dados abstrato que simula um ponteiro e fornece recursos adicionais, como coleta automática de lixo ou verificação de limites. Esses recursos adicionais visam reduzir os erros causados ​​pelo uso indevido dos ponteiros, mantendo a eficiência. Ponteiros inteligentes normalmente acompanham os objetos para os quais apontam para fins de gerenciamento de memória.

O uso indevido de ponteiros é uma das principais fontes de erros: a alocação, desalocação e referência constantes que devem ser executadas por um programa escrito usando ponteiros introduz o risco de que ocorram vazamentos de memória. Ponteiros inteligentes tentam impedir vazamentos de memória, tornando a desalocação automática de recursos: quando o ponteiro (ou o último de uma série de ponteiros) para um objeto é destruído, por exemplo, porque fica fora do escopo, o objeto apontado também é destruído.

Em C ++, a ênfase seria na coleta de lixo e na prevenção de vazamentos de memória (apenas para citar dois). Os ponteiros são uma parte fundamental da linguagem, portanto, não é impossível usá-los, exceto nos programas mais trivalentes.


22
tipicamente não é estritamente coleta de lixo no sentido clássico, mais contagem de referência. Pelo menos no PTR inteligente Estou acostumado a ([impulso | std] :: shared_ptr)
Doug T.

3
Essa resposta é muito limitada ao ponteiro inteligente, que é apenas um pequeno aspecto do problema. Além disso, o ponteiro inteligente não é uma coleta de lixo.
deadalnix

3
Também RAII em geral.
Klaim

3
Não, não é isso que se entende. É apenas um aspecto, e não o mais importante.
21978 Konrad Rudolph

Penso que indicadores inteligentes são o aspecto mais importante, mas concordo que existem muitos outros.
DeadMG 01/09/12

97

Já que publiquei a polêmica “não use ponteiros de merda” , sinto que devo comentar aqui.

Antes de tudo, como polêmica, obviamente representa um ponto de vista extremo. Não são definitivamente usos legítimos de ponteiros (matérias). Mas eu (e muitos programadores profissionais de C ++) sustentamos que esses casos são extremamente raros. Mas o que realmente queremos dizer é o seguinte:

Primeiro:

Os ponteiros brutos não devem, em circunstância alguma, possuir memória.

Aqui, “própria memória” significa essencialmente que em algum momento deleteé chamado nesse ponteiro (mas é mais geral que isso). Esta declaração pode ser tomada com segurança como absoluta. A única exceção é ao implementar seu próprio ponteiro inteligente (ou outra estratégia de gerenciamento de memória). E mesmo lá, você normalmente ainda deve usar um ponteiro inteligente em nível baixo.

A lógica para isso é bastante simples: ponteiros brutos que possuem memória introduzem uma fonte de erro. E esses erros são prolíficos no software existente: vazamentos de memória e exclusão dupla - ambos uma consequência direta da propriedade pouco clara dos recursos (mas indo na direção oposta).

Esse problema pode ser completamente eliminado, praticamente sem nenhum custo, simplesmente usando ponteiros inteligentes em vez de ponteiros brutos (ressalva: isso ainda requer pensamento, é claro; ponteiros compartilhados podem levar a ciclos e, portanto, mais uma vez a vazamentos de memória - mas isso é facilmente evitável).

Segundo:

A maioria dos usos de ponteiros em C ++ é desnecessária.

Diferente de outras linguagens, o C ++ tem um suporte muito forte à semântica de valores e simplesmente não precisa da indireta de ponteiros. Isso não foi percebido imediatamente - historicamente, o C ++ foi inventado para facilitar a orientação fácil de objetos em C e dependia muito da construção de gráficos de objetos conectados por ponteiros. Mas no C ++ moderno, esse paradigma raramente é a melhor escolha, e os idiomas modernos do C ++ geralmente não precisam de ponteiros . Eles operam com valores e não com indicadores.

Infelizmente, essa mensagem ainda não foi capturada em grande parte da comunidade de usuários do C ++. Como resultado, a maior parte do código C ++ que está escrito ainda está repleta de ponteiros supérfluos que tornam o código complexo, lento e com defeito / não confiável.

Para alguém que sabe moderna C ++, é claro que você muito raramente precisam de quaisquer ponteiros (quer inteligentes ou matérias; exceto quando usá-los como iteradores). O código resultante é mais curto, menos complexo, mais legível, geralmente mais eficiente e mais confiável.


5
Suspiro ... esta deve realmente ser a resposta com mais de 30 votos positivos ... especialmente para o segundo ponto. Os ponteiros raramente são necessários no C ++ moderno.
Charles Salvia

1
que tal por exemplo. O objeto Gui possui vários objetos de documento. Ele possui esses indicadores como ponteiros, para que a classe possa ser declarada para frente (evita recompilações) e para que o objeto possa ser inicializado quando criado (com novo), em vez de ser criado na pilha em algum estado vazio e depois arquivado posteriormente? Isso parece um uso perfeitamente válido de ponteiros - todos os objetos encapsulados não deveriam estar na pilha?
Martin Beckett

4
Os objetos GUI do @Martin são um caso em que os gráficos de objetos de ponteiro são realmente a melhor solução. Mas o decreto contra os indicadores brutos que possuem memória ainda permanece. Use ponteiros compartilhados ou desenvolva um modelo de propriedade adequado e tenha apenas relações fracas (= não proprietárias) entre os controles por meio de ponteiros brutos.
Konrad Rudolph

1
@ VF1 std::unique_ptr. Além disso, por que não ptr_vec? Mas geralmente um vetor de valor ainda será trocado mais rapidamente (especialmente com a semântica de movimentação).
Konrad Rudolph

1
@gaazkam Ninguém o forçou a usar apenas contêineres padrão. O Boost, por exemplo, possui uma implementação de mapa com suporte para tipos incompletos. Outra solução é usar o apagamento de tipo; boost::variantcom a recursive_wrapperé provavelmente a minha solução favorita para representar um DAG.
Konrad Rudolph

15

Simplesmente porque existem abstrações disponíveis que ocultam os aspectos mais temperamentais do uso de ponteiros, como acesso à memória não processada e limpeza após as alocações. Com ponteiros inteligentes, classes de contêiner e padrões de design como RAII, a necessidade de usar ponteiros brutos é diminuída. Dito isto, como qualquer abstração, você deve entender como eles realmente funcionam antes de ir além deles.


11

Relativamente simples, a mentalidade C é "Você tem um problema? Use um ponteiro". Você pode ver isso em cadeias C, ponteiros de função, ponteiros como iteradores, ponteiro para ponteiro, ponteiro nulo - mesmo nos primeiros dias do C ++ com ponteiros membros.

Mas em C ++ você pode usar valores para muitas ou todas essas tarefas. Precisa de uma abstração de função? std::function. É um valor que é uma função. std::string? É um valor, é uma string. Você pode ver abordagens semelhantes em todo o C ++. Isso torna a análise do código muito mais fácil para humanos e compiladores.


Em C ++: tem um problema? Use um ponteiro. Agora você tem 2 problemas ...
Daniel Zazula

10

Um dos motivos é a aplicação muito ampla de ponteiros. Eles podem ser usados ​​para iteração em contêineres, para evitar a cópia de objetos grandes ao passar para a função, gerenciamento não trivial do tempo de vida, acesso a lugares aleatórios na memória, etc. imediatamente independentemente da intenção.

A seleção de uma ferramenta para fins exatos torna o código mais simples e mais visível - iteradores para iterações, indicadores inteligentes para gerenciamento de toda a vida útil, etc.


3

Além dos motivos já listados, existe um óbvio: melhores otimizações. A análise de aliasing é muito complicada na presença de uma aritmética de ponteiro, enquanto as referências sugerem um otimizador, portanto, uma análise de aliasing muito mais profunda é possível se apenas referências forem usadas.


2

Além do risco de vazamento de memória declarado pelo @jmquigley, a ponteiro e a aritmética do ponteiro podem ser consideradas problemáticas porque os ponteiros podem apontar para qualquer lugar da memória, causando "erros difíceis de encontrar" e "vulnerabilidades de segurança".

É por isso que eles foram quase abandonados em C # e Java.


Espere que eles não tenham sido "abandonados" em C #. Essa resposta é ruim, tem uma ortografia horrível e uma declaração imprecisa e horrível.
Ramhound

1
Eles estavam quase abandonados. Peço desculpas por não ser um falante nativo.
K3b

1
Ei, nós podemos ajudar com a ortografia. Você quis dizer esperar ou exceto?
DeveloperDon

1
Quase abandonado em C #, você ainda pode ativar o ponteiro especificando unsafepalavra-chave
linquize

-1

O C ++ suporta a maioria dos recursos C , além de Objetos e Classes. C já tinha ponteiros e outras coisas.

Os ponteiros são uma técnica muito útil, que pode ser combinada com a Orientação a objetos, e o C ++ os suporta. Porém, essa técnica é difícil de ensinar e difícil de entender e é muito fácil causar erros indesejados.

Muitas novas linguagens de programação fingem não usar ponteiros com objetos, como Java, .NET, Delphi, Vala, PHP, Scala. Mas, ponteiros ainda são usados ​​"nos bastidores". Essas técnicas de "ponteiro oculto" são chamadas de "referências".

Enfim, considero o (s) ponteiro (s) como um Padrão de Programação, como uma maneira válida de resolver certos problemas, assim como a Programação Orientada a Objetos .

Outros desenvolvedores podem ter uma opinião diferente. Mas sugiro que estudantes e programadores aprendam como:

(1) Use ponteiros sem objetos

(2) objetos sem ponteiros

(3) ponteiros explícitos para objetos

(4) ponteiros "ocultos" para objetos ( referência AKA ) ;-)

Naquela ordem.

Mesmo que seja difícil de ensinar e difícil de aprender. O objeto Pascal (Delphi, FreePascal, outros) e C++(não Java ou C #) pode ser usado para esses objetivos.

E, mais tarde, programadores iniciantes, podem passar para "ponteiros ocultos para objetos" linguagens de programação como: Java, C #, PHP orientado a objetos e outros.


19
C ++ é muito mais do que o "C com Classes" que começou como.
David Thornley

Por que você está agrupando C ++ e C entre aspas? E "oculto", "referências" e tudo mais? Você é um "vendedor" e não participa de "programação"?
Phresnel #

Eu deveria colocá-los em negrito. Cotações pode ser usado para destacar, mas também para o oposto
umlcat

-6

Falando sobre o VC6, quando você lança um ponteiro de uma classe (que você instancia) em uma variável (por exemplo, DWORD), mesmo que esse ponteiro seja local, você pode acessar a classe por todas as funções que usam o mesmo heap. A classe instanciada é definida como local, mas na verdade não é. Até onde eu sei, qualquer endereço de uma variável, estrutura ou classe de pilha é único ao longo de toda a vida útil da classe de hospedagem.

Exemplo:

class MyClass1 {
    public:
        void A (void);
        void B (void);
        void C (void);
    private:
        DWORD dwclass;
};

class MyClass2 {
    public:
        int C (int i);
};

void MyClass1::A (void) {
    MyClass2 *myclass= new MyClass2;
    dwclass=(DWORD)myclass;
}

void MyClass1::B (void) {
    MyClass2 *myclass= (MyClass2 *)dwclass;
    int i = myclass->C(0); // or int i=((MyClass2 *)dwclass)->C(0);
}

void MyClass1::B (void) {
    MyClass2 *myclass= (MyClass2 *)dwclass;
    delete myclass;
}

EDIT Isso é uma parte muito pequena do código original. A classe CSRecodset é apenas uma classe de conversão de CXdbRecordset, onde está todo o código real. Fazendo isso, posso permitir que o usuário se beneficie do que escrevi sem perder meus direitos. Não pretendo demonstrar que meu mecanismo de banco de dados é profissional, mas realmente funciona.

//-------------------------------------
class CSRecordSet : public CSObject
//-------------------------------------
{
public:
    CSRecordSet();
    virtual ~CSRecordSet();
    // Constructor
    bool Create(CSDataBase* pDataBase,CSQueryDef* pQueryDef);
    //Open, find, close
    int OpenRst(bool bReadBlanks=0,bool bCheckLastSql=0,bool bForceLoad=0,bool bMessage=1);     // for a given SQL
    int FindRecord(bool bNext);         // for a given SQL
    // TABLE must be ordered by the same fields that will be seek
    bool SeekRecord(int nFieldIndex, char *key, int length=0);  // CRT bsearch
    bool SeekRecord(int nFieldIndex, long key);     
    bool SeekRecord(int nFieldIndex, double key, int decimals);     
    bool SeekRecord(XSEK *SEK);     
    bool DeleteRecord(void);
    bool Close(void);
    // Record Position:
    bool IsEOF(void);           // pointer out of bound
    bool IsLAST(void);          // TRUE if last record
    bool IsBOF(void);           // pointer out of bound
    bool IsOpen(void);
    bool Move(long lRows);      // returns FALSE if out of bound
    void MoveNextNotEof(void);  // eof is tested
    void MoveNext(void);        // eof is not tested
    void MovePrev(void);        // bof is tested
    void MoveLast(void);
    void MoveFirst(void);
    void SetAbsolutePosition(long lRows);
    long GetAbsolutePosition(void);
    void GoToLast(void); // Restore position after a Filter
    // Table info
    long GetRecordCount(void);
    int GetRstTableNumber(void);
    int GetRecordLength(void); //includes stamp (sizeof char)
    int GetTableType(void);
    // Field info
    int GetFieldCount(void);
    void GetFieldName(int nFieldIndex, char *pbuffer);
    int GetFieldIndex(const char *sFieldName);
    int GetFieldSize(int nFieldIndex);
    int GetFieldDGSize(int nFieldIndex); // String size (i.e. dg_Boolean)
    long GetRecordID(void);
    int GetStandardFieldCount(void);
    bool IsMemoFileTable(void);
    bool IsNumberField(int nFieldIndex);
    int GetFieldType(int nFieldIndex);
    // Read Field value
    bool GetFieldValue(int nFieldIndex, XdbVar& var);
    bool GetFieldValueIntoBuffer(int nFieldIndex,char *pbuffer);
    char *GetMemoField(int nMemoFieldIndex, char *pbuffer, int buf_size);
    bool GetBinaryField(unsigned char *buffer,long *buf_size);
    // Write Field value
    void Edit(void); // required
    bool SetFieldValue(int nFieldIndex, XdbVar& var);
    bool SetFieldValueFromBuffer(int nFieldIndex,const char *pbuffer);
    bool Update(void); // required
    // pointer to the same lpSql
    LPXSQL GetSQL(void);
};

//---------------------------------------------------
CSRecordSet::CSRecordSet(){
//---------------------------------------------------
    pClass |= (CS_bAttach);
}
CSRecordSet::~CSRecordSet(){
    if(pObject) delete (CXdbRecordset*)pObject;
}
bool CSRecordSet::Create(CSDataBase* pDataBase,CSQueryDef* pQueryDef){
    CXdbQueryDef *qr=(CXdbQueryDef*)pQueryDef->GetObject();
    CXdbTables *db=(CXdbTables*)pDataBase->GetObject();
    CXdbRecordset *rst = new CXdbRecordset(db,qr);
    if(rst==NULL) return 0;
    pObject = (unsigned long) rst;
    return 1;
}
bool CSRecordSet::Close(void){
    return ((CXdbRecordset*)pObject)->Close();
}
int CSRecordSet::OpenRst(bool bReadBlanks,bool bCheckLastSql,bool bForceLoad, bool bMessage){
    unsigned long dw=0L;
    if(bReadBlanks) dw|=SQL_bReadBlanks;
    if(bCheckLastSql) dw|=SQL_bCheckLastSql;
    if(bMessage) dw|=SQL_bRstMessage;
    if(bForceLoad) dw|=SQL_bForceLoad;

    return ((CXdbRecordset*)pObject)->OpenEx(dw);
}
int CSRecordSet::FindRecord(bool bNext){
    return ((CXdbRecordset*)pObject)->FindRecordEx(bNext);
}
bool CSRecordSet::DeleteRecord(void){
    return ((CXdbRecordset*)pObject)->DeleteEx();
}
bool CSRecordSet::IsEOF(void){
    return ((CXdbRecordset*)pObject)->IsEOF();
}
bool CSRecordSet::IsLAST(void){
    return ((CXdbRecordset*)pObject)->IsLAST();
}
bool CSRecordSet::IsBOF(void){
    return ((CXdbRecordset*)pObject)->IsBOF();
}
bool CSRecordSet::IsOpen(void){
    return ((CXdbRecordset*)pObject)->IsOpen();
}
bool CSRecordSet::Move(long lRows){
    return ((CXdbRecordset*)pObject)->MoveEx(lRows);
}
void CSRecordSet::MoveNextNotEof(void){
    ((CXdbRecordset*)pObject)->MoveNextNotEof();
}
void CSRecordSet::MoveNext(void){
    ((CXdbRecordset*)pObject)->MoveNext();
}
void CSRecordSet::MovePrev(void){
    ((CXdbRecordset*)pObject)->MovePrev();
}
void CSRecordSet::MoveLast(void){
    ((CXdbRecordset*)pObject)->MoveLast();
}
void CSRecordSet::MoveFirst(void){
    ((CXdbRecordset*)pObject)->MoveFirst();
}
void CSRecordSet::SetAbsolutePosition(long lRows){
    ((CXdbRecordset*)pObject)->SetAbsolutePosition(lRows);
}
long CSRecordSet::GetAbsolutePosition(void){
    return ((CXdbRecordset*)pObject)->m_AbsolutePosition;
}
long CSRecordSet::GetRecordCount(void){
    return ((CXdbRecordset*)pObject)->GetRecordCount();
}
int CSRecordSet::GetFieldCount(void){
    return ((CXdbRecordset*)pObject)->GetFieldCount();
}
int CSRecordSet::GetRstTableNumber(void){
    return ((CXdbRecordset*)pObject)->GetRstTableNumber();
}
void CSRecordSet::GetFieldName(int nFieldIndex, char *pbuffer){
    ((CXdbRecordset*)pObject)->GetFieldName(nFieldIndex,pbuffer);
}
int CSRecordSet::GetFieldIndex(const char *sFieldName){
    return ((CXdbRecordset*)pObject)->GetFieldIndex(sFieldName);
}
bool CSRecordSet::IsMemoFileTable(void){
    return ((CXdbRecordset*)pObject)->IsMemoFileTable();
}
bool CSRecordSet::IsNumberField(int nFieldIndex){
    return ((CXdbRecordset*)pObject)->IsNumberField(nFieldIndex);
}
bool CSRecordSet::GetFieldValueIntoBuffer(int nFieldIndex,char *pbuffer){
    return ((CXdbRecordset*)pObject)->GetFieldValueIntoBuffer(nFieldIndex,pbuffer);
}
void CSRecordSet::Edit(void){
    ((CXdbRecordset*)pObject)->Edit();
}
bool CSRecordSet::Update(void){
    return ((CXdbRecordset*)pObject)->Update();
}
bool CSRecordSet::SetFieldValue(int nFieldIndex, XdbVar& var){
    return ((CXdbRecordset*)pObject)->SetFieldValue(nFieldIndex,var);
}
bool CSRecordSet::SetFieldValueFromBuffer(int nFieldIndex,const char *pbuffer){
    return ((CXdbRecordset*)pObject)->SetFieldValueFromBuffer(nFieldIndex,pbuffer);
}
bool CSRecordSet::GetFieldValue(int nFieldIndex, XdbVar& var){
    return ((CXdbRecordset*)pObject)->GetFieldValue(nFieldIndex,var);
}
bool CSRecordSet::SeekRecord(XSEK *SEK){
    return ((CXdbRecordset*)pObject)->TableSeek(SEK);
}
bool CSRecordSet::SeekRecord(int nFieldIndex,char *key, int length){
    return ((CXdbRecordset*)pObject)->TableSeek(nFieldIndex,key,length);
}
bool CSRecordSet::SeekRecord(int nFieldIndex,long i){
    return ((CXdbRecordset*)pObject)->TableSeek(nFieldIndex,i);
}
bool CSRecordSet::SeekRecord(int nFieldIndex, double d, int decimals)
{
    return ((CXdbRecordset*)pObject)->TableSeek(nFieldIndex,d,decimals);
}
int CSRecordSet::GetRecordLength(void){
    return ((CXdbRecordset*)pObject)->GetRecordLength();
}
char *CSRecordSet::GetMemoField(int nMemoFieldIndex,char *pbuffer, int BUFFER_SIZE){
    return ((CXdbRecordset*)pObject)->GetMemoField(nMemoFieldIndex,pbuffer,BUFFER_SIZE);
}
bool CSRecordSet::GetBinaryField(unsigned char *buffer,long *buf_size){
    return ((CXdbRecordset*)pObject)->GetBinaryField(buffer,buf_size);
}
LPXSQL CSRecordSet::GetSQL(void){
    return ((CXdbRecordset*)pObject)->GetSQL();
}
void CSRecordSet::GoToLast(void){
    ((CXdbRecordset*)pObject)->GoToLast();
}
long CSRecordSet::GetRecordID(void){
    return ((CXdbRecordset*)pObject)->GetRecordID();
}
int CSRecordSet::GetStandardFieldCount(void){
    return ((CXdbRecordset*)pObject)->GetStandardFieldCount();
}
int CSRecordSet::GetTableType(void){
    return ((CXdbRecordset*)pObject)->GetTableType();
}
int CSRecordSet::GetFieldType(int nFieldIndex){
    return ((CXdbRecordset*)pObject)->GetFieldType(nFieldIndex);
}
int CSRecordSet::GetFieldDGSize(int nFieldIndex){
    return ((CXdbRecordset*)pObject)->GetFieldDGSize(nFieldIndex);
}
int CSRecordSet::GetFieldSize(int nFieldIndex){
    return ((CXdbRecordset*)pObject)->GetFieldSize(nFieldIndex);
}

EDIT: solicitado por DeadMG:

void nimportequoidumomentquecaroule(void) {

    short i = -4;
    unsigned short j=(unsigned short)i;

}

1
Essa descrição pode ser aprimorada significativamente por algum código para ilustrar o que você está descrevendo. Sinto que isso está relacionado à pergunta original, mas se você nos alertasse sobre um perigo nesse cenário, ajudaria a elaborar o tópico do questionador.
DeveloperDon

1
Essa conversão para DWORDé abusiva e possivelmente incorreta (DWORD não é necessariamente grande o suficiente para segurar um ponteiro). Se você precisar de um ponteiro não digitado, use void*- mas, quando precisar disso em C ++, geralmente terá um problema de design em seu código, que deverá corrigir.
21412 Mat

Salvador, acho que você está tentando dizer algo sobre o VC6 e como ele tem manipulação incomum e inesperada de ponteiros. O exemplo pode se beneficiar de comentários e, se você estiver vendo avisos do compilador, eles podem ser informativos no que diz respeito a relacionar sua resposta à pergunta.
DeveloperDon

@ Mat Esse exemplo é para um sistema operacional de 32 bits e o compilador VC6 ++.
Salvador

6
Falando sobre código ruim em um compilador absolutamente antigo? Não, obrigado.
DeadMG 01/09/12
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.