¿Por qué no se recomiendan los punteros al codificar con C ++?


45

Leí en alguna parte que cuando se usa C ++ se recomienda no usar punteros. ¿Por qué los punteros son una mala idea cuando estás usando C ++? Para los programadores de C que están acostumbrados a usar punteros, ¿cuál es la mejor alternativa y enfoque en C ++?


40
por favor enlace a "algún lugar". El contexto puede ser muy relevante.

1
Espero que esta pregunta sea ​​útil para usted.
Garet Claborn

La mayoría de estas respuestas se refieren a evitar pérdidas de memoria como la razón principal. No recuerdo la última vez que una de nuestras aplicaciones tuvo un problema de pérdida de memoria a pesar de usar punteros. Si tiene problemas de pérdida de memoria, entonces no está utilizando las herramientas adecuadas o no sabe lo que está haciendo. La mayoría de los entornos de desarrollo tienen una forma de verificar automáticamente si hay fugas incorporadas. Creo que los problemas de pérdida de memoria son mucho más difíciles de rastrear en los idiomas recolectados de basura porque su aparición es mucho más sutil y con frecuencia necesita una herramienta de terceros para rastrear al culpable .
Dunk

1
Además del comentario de @Dunk, a veces los recolectores de basura integrados en idiomas de nivel superior simplemente no funcionan correctamente. El recolector de basura de ActionScript 3 no lo hace, por ejemplo. Hay un error en este momento que tiene que ver con NetConnectioninstancias que se desconectan del servidor ( stackoverflow.com/questions/14780456/… ), así como un problema con la existencia de múltiples objetos en un programa que específicamente se negará a recopilar. ...
Panzercrisis

... ( adobe.com/devnet/actionscript/learning/as3-fundamentals/… - buscar GCRoots are never garbage collected.y el párrafo comenzó por The MMgc is considered a conservative collector for mark/sweep.). Técnicamente, este es un problema en Adobe Virtual Machine 2, no en el AS3 en sí, pero cuando tiene problemas como este en lenguajes de nivel superior, que tienen esencialmente la recolección de basura incorporada, a menudo no tiene ninguna forma verdadera en el lenguaje para depurar estos problemas completamente fuera del programa. ...
Panzercrisis

Respuestas:


58

Creo que significan que debes usar punteros inteligentes en lugar de punteros regulares.

En informática, un puntero inteligente es un tipo de datos abstracto que simula un puntero al tiempo que proporciona características adicionales, como la recolección automática de basura o la verificación de límites. Estas características adicionales están destinadas a reducir los errores causados ​​por el mal uso de los punteros mientras se mantiene la eficiencia. Los punteros inteligentes generalmente realizan un seguimiento de los objetos a los que apuntan con el fin de administrar la memoria.

El mal uso de los punteros es una fuente importante de errores: la asignación constante, la desasignación y las referencias que debe realizar un programa escrito con punteros introduce el riesgo de que ocurran pérdidas de memoria. Los punteros inteligentes intentan evitar pérdidas de memoria al hacer que la desasignación de recursos sea automática: cuando el puntero (o el último de una serie de punteros) a un objeto se destruye, por ejemplo, porque sale del alcance, el objeto puntiagudo también se destruye.

En C ++, el énfasis estaría en la recolección de basura y la prevención de pérdidas de memoria (solo por nombrar dos). Los punteros son una parte fundamental del lenguaje, por lo que no usarlos es prácticamente imposible, excepto en los programas más triviales.


22
típicamente no es estrictamente recolección de basura en el sentido clásico, más recuento de referencias. Al menos en el ptr inteligente al que estoy acostumbrado ([boost | std] :: shared_ptr)
Doug T.

3
Esta respuesta está muy limitada al puntero inteligente, que es solo un pequeño aspecto del problema. Además, el puntero inteligente no es recolección de basura.
deadalnix

3
También RAII en general.
Klaim

3
No, esto no es lo que significa. Es solo un aspecto, y no el más importante.
Konrad Rudolph el

Creo que los punteros inteligentes son el aspecto más importante, pero estoy de acuerdo en que hay muchos otros.
DeadMG

97

Como yo fui quien publicó la polémica "no use punteros" , siento que debería comentar aquí.

En primer lugar, como polémica, obviamente representa un punto de vista extremo. No son definitivamente los usos legítimos de punteros (crudos). Pero yo (y muchos programadores profesionales de C ++) sostienen que estos casos son extremadamente raros. Pero lo que realmente queremos decir es lo siguiente:

Primero:

Los punteros en bruto no deben en ningún caso poseer memoria.

Aquí, "memoria propia" esencialmente significa que en algún momento deletese llama a ese puntero (pero es más general que eso). Esta declaración se puede tomar con seguridad como un absoluto. La única excepción es cuando implementa su propio puntero inteligente (u otra estrategia de administración de memoria). Y hasta allí se debe normalmente todavía utilizar un puntero inteligente en el nivel bajo.

La razón para esto es bastante simple: los punteros sin procesar que poseen memoria introducen una fuente de error. Y estos errores son prolíficos en el software existente: fugas de memoria y doble eliminación, ambas una consecuencia directa de la propiedad de recursos poco clara (pero en dirección opuesta).

Este problema se puede eliminar por completo, prácticamente sin costo, simplemente usando punteros inteligentes en lugar de punteros sin procesar (advertencia: esto todavía requiere pensar, por supuesto; los punteros compartidos pueden conducir a ciclos y, por lo tanto, una vez más a pérdidas de memoria, pero esto es fácil evitable).

Segundo:

La mayoría de los usos de punteros en C ++ son innecesarios.

A diferencia de otros lenguajes, C ++ tiene un soporte muy fuerte para la semántica de valor y simplemente no necesita la indirecta de los punteros. Esto no se realizó de inmediato: históricamente, C ++ se inventó para facilitar la orientación fácil de objetos en C, y se basó en gran medida en la construcción de gráficos de objetos que estaban conectados por punteros. Pero en C ++ moderno, este paradigma rara vez es la mejor opción, y los modismos modernos de C ++ a menudo no necesitan punteros . Operan en valores en lugar de punteros.

Desafortunadamente, este mensaje aún no se ha aplicado en gran parte de la comunidad de usuarios de C ++. Como resultado, la mayor parte del código de C ++ que está escrito todavía está lleno de punteros superfluos que hacen que el código sea complejo, lento y defectuoso / poco confiable.

Para alguien que conoce moderna C ++, está claro que muy raramente necesita ningún punteros inteligentes (ya sea o primas, excepto cuando se utiliza como iteradores). El código resultante es más corto, menos complejo, más legible, a menudo más eficiente y más confiable.


55
Suspiro ... esta debería ser la respuesta con más de 30 votos a favor ... especialmente para el segundo punto. Los punteros rara vez son necesarios incluso en C ++ moderno.
Charles Salvia

1
¿Qué pasa, por ejemplo. El objeto Gui posee un montón de objetos doc. Tiene estos como punteros para que la clase se pueda declarar hacia adelante (evita recompilaciones) y para que el objeto se pueda inicializar cuando se crea (con nuevo) en lugar de crearse en la pila en un estado vacío y luego archivarse más tarde. Esto parece un uso perfectamente válido de punteros: ¿no deberían estar todos los objetos encapsulados en el montón?
Martin Beckett

44
Los objetos de la interfaz gráfica de usuario de @Martin son un caso en el que los gráficos de objetos de puntero son la mejor solución. Pero el edicto contra los punteros crudos que poseen memoria todavía se mantiene. Utilice punteros compartidos en todo momento o desarrolle un modelo de propiedad adecuado y solo tenga relaciones débiles (= no propias) entre los controles a través de punteros sin formato.
Konrad Rudolph

1
@ VF1 std::unique_ptr. Además, ¿por qué no ptr_vec? Pero por lo general, un vector de valores con todavía se intercambiará más rápido (especialmente con semántica de movimiento).
Konrad Rudolph el

1
@gaazkam Nadie te obligó a usar solo contenedores estándar. Boost, por ejemplo, tiene una implementación de mapa con soporte para tipos incompletos. Otra solución es usar el tipo de borrado; boost::variantcon a recursive_wrapperes probablemente mi solución favorita para representar un DAG.
Konrad Rudolph

15

Simplemente porque hay abstracciones disponibles que ocultan los aspectos más temperamentales del uso de punteros, como el acceso a la memoria sin procesar y la limpieza después de las asignaciones. Con punteros inteligentes, clases de contenedores y patrones de diseño como RAII, la necesidad de usar punteros sin procesar se reduce. Dicho esto, como cualquier abstracción, debes entender cómo funcionan realmente antes de ir más allá de ellos.


11

Relativamente simple, la mentalidad C es "¿Tienes un problema? Usa un puntero". Puede ver esto en cadenas C, punteros de función, punteros como iteradores, puntero a puntero, puntero vacío, incluso en los primeros días de C ++ con punteros miembros.

Pero en C ++ puede usar valores para muchas o todas estas tareas. ¿Necesitas una función de abstracción? std::function. Es un valor que es una función. std::string? Es un valor, es una cadena. Puede ver enfoques similares en todo C ++. Esto hace que analizar el código sea mucho más fácil tanto para humanos como para compiladores.


En C ++: ¿Tienes un problema? Usa un puntero. Ahora tienes 2 problemas ...
Daniel Zazula

10

Una de las razones es la aplicación demasiado amplia de punteros. Se pueden usar para la iteración sobre contenedores, para evitar copiar objetos grandes al pasar a la función, administración de tiempo de vida no trivial, acceder a lugares aleatorios en la memoria, etc. Y una vez que los usó para un propósito, otras características están disponibles. inmediatamente independientemente de la intención.

La selección de una herramienta para el propósito exacto hace que el código sea más simple y la intención más visible: iteradores para iteraciones, punteros inteligentes para la gestión de la vida útil, etc.


3

Además de las razones ya mencionadas, hay una obvia: mejores optimizaciones. El análisis de alias es demasiado complicado en presencia de una aritmética de puntero, mientras que las referencias sugieren un optimizador, por lo que es posible un análisis de alias mucho más profundo si solo se usan referencias.


2

Además del riesgo de pérdidas de memoria indicadas por @jmquigley, el puntero y la aritmética del puntero pueden considerarse problemáticos porque los punteros pueden apuntar a todas partes en la memoria causando "errores difíciles de encontrar" y "vulnerabilidades de seguridad".

Es por eso que casi fueron abandonados en C # y Java.


Espere que no hayan sido "anulados" en C #. Esta respuesta es pobre, tiene una ortografía horrible y una declaración inexacta horrible.
Ramhound

1
Estaban casi abandonados. Pido disculpas por no ser un hablante nativo.
k3b

1
Hola, podemos ayudar con la ortografía. ¿Querías decir esperar o excepto?
DesarrolladorDon

1
Casi abandonado en C #, aún puede habilitar el puntero especificando la unsafepalabra clave
linquize

-1

C ++ es compatible con la mayoría de C , características, además de objetos y clases. C ya tenía punteros y otras cosas.

Los punteros son una técnica muy útil, que se puede combinar con la orientación a objetos, y C ++ los admite. Pero esta técnica es difícil de enseñar y de entender, y es muy fácil causar errores no deseados.

Muchos lenguajes de programación nuevos pretenden no utilizar punteros con objetos, como Java, .NET, Delphi, Vala, PHP, Scala. Pero, los punteros todavía se usan, "detrás de escena". Estas técnicas de "puntero oculto" se denominan "referencias".

De todos modos, considero puntero (s) como un patrón de programación, como una forma válida de resolver ciertos problemas, así como lo hace la programación orientada a objetos .

Otros desarrolladores pueden tener una opinión diferente. Pero, sugiero que los estudiantes y programadores aprendan cómo:

(1) Use punteros sin objetos

(2) objetos sin punteros

(3) punteros explícitos a objetos

(4) punteros "ocultos" a los objetos ( referencia AKA ) ;-)

En ese orden.

Incluso si es difícil de enseñar y difícil de aprender. Object Pascal (Delphi, FreePascal, otros) y C++(no Java o C #) se pueden usar para esos objetivos.

Y, más tarde, los programadores novatos pueden pasar a lenguajes de programación de "punteros ocultos a objetos" como: Java, C #, PHP orientado a objetos y otros.


19
C ++ es mucho más que la "C con clases" como comenzó.
David Thornley

¿Por qué estás envolviendo C ++ y C en comillas aéreas? ¿Y "oculto", "referencias" y todo lo demás? ¿Es usted un "vendedor" y no participa en la "programación"?
phresnel

Debería ponerlos en negrita. Las citas se pueden usar para resaltar pero también para lo contrario
umlcat el

-6

Hablando de VC6, cuando convierte un puntero de una clase (que crea una instancia) en una variable (por ejemplo, DWORD), incluso si este puntero es local, puede acceder a la clase sobre todas las funciones que usan el mismo montón. La clase instanciada se define como local pero, de hecho, no lo es. Hasta donde sé, cualquier dirección de una variable, estructura o clase de montón es única a lo largo de toda la vida de la clase de alojamiento.

Ejemplo:

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;
}

EDITAR Esa es una muy pequeña parte del código original. La clase CSRecodset es solo una clase de conversión de CXdbRecordset, donde está todo el código real. Al hacerlo, puedo permitir que el usuario se beneficie de lo que escribí sin perder mis derechos. No pretendo demostrar que mi motor de base de datos es profesional, pero 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);
}

EDITAR: solicitado por DeadMG:

void nimportequoidumomentquecaroule(void) {

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

}

1
Esta descripción podría mejorarse significativamente mediante algún código para ilustrar lo que está describiendo. Tengo la sensación de que está relacionado con la pregunta original, pero si nos advirtió de un peligro en este escenario, ayudaría a elaborar el tema del interlocutor.
DesarrolladorDon

1
Ese elenco DWORDes abusivo y posiblemente incorrecto (DWORD no es necesariamente lo suficientemente ancho como para sostener un puntero). Si necesita un puntero sin tipo, úselo, void*pero cuando lo necesite en C ++, a menudo tiene un problema de diseño en su código que debe solucionar.
Mat

Salvador, creo que estás tratando de decir algo sobre VC6 y cómo tiene un manejo de puntero inusual e inesperado. El ejemplo podría beneficiarse con los comentarios, y si está viendo advertencias del compilador, podrían ser informativos con respecto a relacionar su respuesta a la pregunta.
DesarrolladorDon

@Mat Ese ejemplo es para un sistema operativo de 32 bits y el compilador VC6 ++.
Salvador

66
¿Hablando de código incorrecto en un compilador absolutamente antiguo? No, gracias.
DeadMG
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.