Чому покажчики не рекомендуються при кодуванні C ++?


45

Я десь прочитав, що при використанні C ++ рекомендується не використовувати вказівники. Чому вказівники такі погані ідеї, коли ви використовуєте C ++. Для програмістів на C, які звикли використовувати покажчики, яка краща альтернатива та підхід у C ++?


40
будь ласка, посилайтесь на "десь". Контекст може бути дуже актуальним.

1
Це питання , сподіваємось, корисне для вас.
Гарет Клаборн

Більшість із цих відповідей стосуються уникнення витоку пам'яті як основної причини. Я не можу пригадати, коли востаннє в одному з наших додатків виникла пам'ять, незважаючи на використання покажчиків. Якщо у вас є проблеми з витоком пам'яті, ви не використовуєте правильні інструменти або не знаєте, що робите. У більшості середовищ розробки є спосіб автоматично перевірити наявність витоків. Я думаю, що проблеми з витоком пам’яті набагато складніше відстежувати на зібраних сміттях мовах, оскільки їх поява набагато тонкіша, і вам часто потрібен інструмент сторонніх розробників для пошуку винуватця. .
Данк

1
Додаючи до коментаря @Dunk, іноді вбудовані сміттєзбірники мовами вищого рівня просто не працюють правильно. Наприклад, збирач сміття ActionScript 3 не працює. Зараз у ньому є помилка, що стосується NetConnectionвипадків, що відключаються від сервера ( stackoverflow.com/questions/14780456/… ), а також проблема з тим, що в програмі є кілька об'єктів, які спеціально відмовляться від збирання ...
Panzercrisis

... ( adobe.com/devnet/actioncript/learning/as3-fundamentals/… - пошук GCRoots are never garbage collected.та абзац, розпочатий з The MMgc is considered a conservative collector for mark/sweep.). Технічно це проблема в Adobe Virtual Machine 2, а не в самому AS3, але коли у вас є подібні проблеми в мовах вищого рівня, в яких в основному вбудований збір сміття, у вас часто немає справжнього способу налагодити мову. ці питання повністю вийшли з програми. ...
Panzercrisis

Відповіді:


58

Я думаю, що вони означають, що ви повинні використовувати розумні покажчики замість звичайних покажчиків.

В інформатиці розумний покажчик - це абстрактний тип даних, який імітує вказівник, надаючи додаткові функції, такі як автоматичне збирання сміття або перевірка меж. Ці додаткові функції призначені для зменшення помилок, спричинених неправильним використанням покажчиків, зберігаючи ефективність. Інтелектуальні покажчики зазвичай відслідковують об'єкти, на які вони вказують, з метою управління пам'яттю.

Неправильне використання покажчиків є основним джерелом помилок: постійне розподілення, розміщення та посилання, яке повинно виконувати програма, написана за допомогою покажчиків, вводить ризик виникнення витоку пам'яті. Розумні покажчики намагаються запобігти витоку пам’яті, зробивши розстановку ресурсів автоматичною: коли вказівник (або останній у ряді покажчиків) на об’єкт знищений, наприклад, тому що він виходить за межі, загострений об’єкт також знищується.

У C ++ акцент робиться на збиранні сміття та запобіганні витоку пам’яті (просто назвати два). Покажчики є основоположною частиною мови, тому не використовувати їх майже неможливо, за винятком більшості програм.


22
як правило, це не суворо збирання сміття в класичному розумінні, більш довідковий підрахунок. Принаймні, у розумному ptr, до якого я звик ([boost | std] :: shared_ptr)
Doug T.

3
Ця відповідь дуже обмежена розумним покажчиком, що є лише невеликим аспектом проблеми. Крім того, розумний покажчик не є збиранням сміття.
deadalnix

3
Також RAII загалом.
Клаїм

3
Ні, це не те, що мається на увазі. Це лише один аспект, і не найважливіший.
Конрад Рудольф

Я думаю, що розумні покажчики є найважливішим аспектом, але я згоден, що є багато інших.
DeadMG

97

Оскільки я є тим, хто опублікував полемічну «не використовувати покажчики», я вважаю, що мені слід тут прокоментувати.

Перш за все, як полеміка вона, очевидно, представляє крайню точку зору. Там є , безумовно , законним використанням (сировини) покажчиків. Але я (і багато професійних програмістів на C ++) вважаю, що такі випадки надзвичайно рідкісні. Але ми маємо на увазі наступне:

Спочатку:

Сирі покажчики ні в якому разі не повинні володіти пам’яттю.

Тут "власна пам'ять" по суті означає, що в якийсь момент deleteвикликається цей покажчик (але це більш загальне, ніж це). Це твердження можна сміливо сприймати як абсолютне. Тільки виняток при реалізації власного інтелектуального покажчика (або стратегії управління іншій пам'яті). І навіть там, як правило, все-таки слід використовувати розумний покажчик на низькому рівні.

Обґрунтування цього досить просте: сировинні покажчики, які мають власну пам’ять, вводять джерело помилок. І ці помилки є помітними в існуючому програмному забезпеченні: витоки пам’яті та подвійне видалення - як прямий наслідок неясного володіння ресурсами (але йдуть у зворотному напрямку).

Цю проблему можна повністю усунути практично без витрат, просто скориставшись розумними покажчиками замість необроблених покажчиків (застереження: звичайно, це все ще потребує роздумів; загальні вказівники можуть призвести до циклів і, таким чином, ще раз до витоку пам'яті - але це легко уникнути).

Друге:

Більшість випадків використання покажчиків на C ++ є непотрібними.

На відміну від інших мов, C ++ має дуже сильну підтримку ціннісної семантики і просто не потребує опосередкування покажчиків. Це було не відразу зрозуміло - історично C ++ був винайдений для полегшення орієнтації на об'єкт у C та багато в чому спирався на побудову об'єктних графіків, пов'язаних між собою покажчиками. Але в сучасній C ++ ця парадигма рідко є найкращим вибором, а сучасним ідіомам C ++ часто взагалі не потрібні покажчики . Вони діють на значеннях, а не на покажчиках.

На жаль, це повідомлення досі не набуло значної частини у спільноті користувачів C ++. Як результат, більша частина записаного коду C ++ все ще засмічена зайвими покажчиками, які роблять код складним, повільним та несправним / ненадійним.

Хтось, хто знає сучасний C ++, зрозуміло, що вам дуже рідко потрібні будь-які вказівники (розумні чи сировинні; за винятком випадків, коли ви використовуєте їх як ітератори). Отриманий код коротший, менш складний, більш читабельний, часто більш ефективний та надійний.


5
Зітхніть ... це справді має бути відповідь з 30+ оновлень ... особливо для другого пункту. Покажчики дуже рідко навіть потрібні в сучасному C ++.
Чарльз Сальвія

1
як щодо напр. Об'єкту Gui належить купа об'єктів doc. Вони мають як покажчики, і тому клас можна оголосити вперед (уникає перекомпіляцій), і таким чином об'єкт можна ініціалізувати, коли його створити (з новим), а не створити на стеці в якомусь порожньому стані, а потім подати до нього? Здається, цілком правильне використання покажчиків - чи не повинні всі капсульовані об’єкти знаходитися на купі?
Мартін Бекетт

4
Об'єкти GUI @Martin - це один випадок, коли графіки об’єктних покажчиків справді є найкращим рішенням. Але едикт проти сирих вказівників, що володіють пам'яттю, все ще стоїть. Або використовуйте загальні вказівники на всьому протязі, або розробляйте належну модель власності і матимете лише слабкі (= невласницькі) зв’язки між елементами управління через необроблені покажчики.
Конрад Рудольф

1
@ VF1 std::unique_ptr. Також, чому б і ні ptr_vec? Але зазвичай вектор значення з все одно буде змінюватися швидше (особливо з семантикою переміщення).
Конрад Рудольф

1
@gaazkam Ніхто не змушував вас використовувати лише стандартні контейнери. Наприклад, Boost має реалізацію карти з підтримкою неповних типів. Іншим рішенням є використання стирання типу; boost::variantз a recursive_wrapper- це, мабуть, моє улюблене рішення представити DAG.
Конрад Рудольф

15

Просто тому, що для вас доступні абстракції, які приховують більш темпераментні аспекти використання покажчиків, наприклад, доступ до сирої пам’яті та очищення після виділення. Із розумними вказівниками, класами контейнерів та моделями дизайну, такими як RAII, потреба у використанні необроблених покажчиків зменшується. Це говорить, як і будь-яка абстракція, ви повинні зрозуміти, як вони насправді працюють, перш ніж вийти за межі них.


11

Відносно просто, ментальність C - "Виникла проблема? Використовуйте покажчик". Це можна побачити в C рядках, функціональних покажчиках, покажчиках як ітераторів, покажчиках на вказівниках, пустотілих вказівниках - навіть у перші дні C ++ за допомогою покажчиків членів.

Але в C ++ ви можете використовувати значення для багатьох або всіх цих завдань. Потрібна абстракція функції? std::function. Це значення, яке є функцією. std::string? Це значення, це рядок. Ви можете побачити подібні підходи в усьому C ++. Це робить аналіз коду набагато простішим як для людей, так і для компіляторів.


В C ++: Виникли проблеми? Використовуйте вказівник. Зараз у вас є 2 проблеми ...
Даніель Зазула

10

Однією з причин є занадто широке застосування покажчиків. Вони можуть бути використані для ітерації над контейнерами, для уникнення копіювання великих об'єктів при переході до функції, нетривіального управління життям, доступу до випадкових місць у пам'яті тощо. І коли ви використовували їх з однією метою, інші їх функції стають доступними негайно самостійно за наміром.

Вибір інструменту з точною метою робить код простішим та наміром більш помітним - ітератори для ітерацій, розумні покажчики для управління життєвим часом тощо.


3

Окрім уже перелічених причин, існує очевидний варіант: кращі оптимізації. Аналіз псевдоніму є надто складним у поданні арифметики вказівника, тоді як посилання натякає на оптимізатор, тому набагато глибший аналіз псевдоніму можливий, якщо використовуються лише посилання.


2

Окрім ризику витоку пам’яті, заявленого за допомогою @jmquigley, покажчик та арифметика вказівника може вважатися проблематичним, оскільки вказівники можуть вказувати всюди в пам’яті, викликаючи «важко знайти помилки» та «вразливості безпеки».

Ось чому їх майже покинули в C # та Java.


Очікуйте, що вони не були "покинуті" в C #. Ця відповідь погана, має жахливе написання та жахливе неточне твердження.
Рамхаунд

1
Вони були майже покинуті. Прошу вибачення за те, що я не став носієм мови.
k3b

1
Гей, ми можемо допомогти з написанням. Ви мали на увазі сказати очікувати чи виключити?
DeveloperDon

1
Майже покинутий у C #, ви все ще можете ввімкнути покажчик, вказавши unsafeключове слово
linquize

-1

C ++ підтримує більшість C , функцій, плюс об'єкти та класи. C вже мав покажчики та інші речі.

Покажчики - це дуже корисна техніка, яку можна поєднувати з орієнтацією на об'єкти, а C ++ підтримує їх. Але цю техніку важко навчити і важко зрозуміти, і її дуже легко викликати небажані помилки.

Багато нових мов програмування роблять вигляд, що не використовують покажчики з такими об'єктами, як Java, .NET, Delphi, Vala, PHP, Scala. Але, покажчики все ще використовуються «поза кадром». Ці методи "прихованого вказівника" називаються "посиланнями".

У будь-якому випадку, я вважаю, що покажчики (і) є "Шаблоном програмування", як дійсний спосіб вирішення певних проблем, як і об'єктно-орієнтоване програмування .

Інші розробники можуть мати іншу думку. Але я пропоную студентам та програмістам навчитися:

(1) Використовуйте покажчики без об'єктів

(2) об'єкти без покажчиків

(3) явні покажчики на об’єкти

(4) "приховані" покажчики на об'єкти ( посилання AKA ) ;-)

У тому порядку.

Навіть якщо це важко вчити, і важко вчитися. Для цих цілей можна використовувати об’єкт Pascal (Delphi, FreePascal, інші) та C++(не Java або C #).

А пізніше початківці програмісти можуть переходити до «прихованих покажчиків на об’єкти» мов програмування, таких як: Java, C #, PHP та інших.


19
C ++ - це набагато більше, ніж "C з класами", з якого він почався.
Девід Торнлі

Чому ви загортаєте C ++ і C у котирування повітря? А «приховані», «посилання» та все інше? Ви "продавець" і не приймаєте участі в "програмуванні"?
френель

Я повинен їх викласти жирним шрифтом. Цитати можна використовувати для виділення, але й протилежного
umlcat

-6

Якщо говорити про VC6, коли ви передаєте вказівник класу (який ви інстанціонуєте) на змінну (наприклад, DWORD), навіть якщо цей покажчик є локальним, ви можете отримати доступ до класу за всіма функціями, які використовують ту саму купу. Екземплярний клас визначається як локальний, але насправді це не так. Наскільки мені відомо, будь-яка адреса змінної, структури чи класу купи є унікальною протягом усього життя хостинг класу.

Приклад:

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 Це дуже мала частина початкового коду. Клас CSRecodset - це лише клас кастингу CXdbRecordset, де є весь реальний код. Таким чином я можу дозволити користувачеві скористатися тим, що я написав, не втрачаючи прав. Я не претендую на те, щоб продемонструвати, що мій механізм бази даних є професійним, але він справді працює.

//-------------------------------------
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: запитується DeadMG:

void nimportequoidumomentquecaroule(void) {

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

}

1
Цей опис можна значно покращити деяким кодом, щоб проілюструвати те, що ви описуєте. У мене є відчуття, що це стосувалося первинного питання, але якщо ви попередили нас про небезпеку в цьому сценарії, це допоможе розібратися в темі запитувача.
DeveloperDon

1
Цей виклик DWORDє образливим і, можливо, неправильним (DWORD не обов'язково достатньо широкий, щоб утримувати покажчик). Якщо вам потрібен нетипізований покажчик, використовуйте void*- але коли ви виявите, що це потрібно в C ++, у вас часто виникають проблеми з дизайном у вашому коді, які ви повинні виправити.
Мат

Сальвадор, я думаю, ти намагаєшся сказати щось про VC6 та про те, як він має незвичне та несподіване поводження з покажчиком. Приклад може принести користь коментарям, і якщо ви бачите попередження від компілятора, вони можуть бути інформативними щодо того, що стосується вашої відповіді на питання.
DeveloperDon

@Mat Цей приклад для 32-бітної ОС та компілятора VC6 ++.
Сальвадор

6
Якщо говорити про поганий код в абсолютно стародавньому компіляторі? Ні, дякую.
DeadMG
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.