CommandConquerゲヌムコヌド90幎代のバグ。第2å·»

image1.png


アメリカの䌚瀟ElectronicArts IncEAは、ゲヌムCommandConquerTiberian DawnおよびCommandConquerRedAlertのオヌプン゜ヌスコヌドをリリヌスしたした。PVS-Studioアナラむザヌを䜿甚した゜ヌスコヌドで数十のバグが芋぀かりたした。芋぀かった欠陥の説明の続きを歓迎したす。



前曞き



CommandConquerは、リアルタむム戊略のゞャンルの䞀連のコンピュヌタヌゲヌムです。シリヌズの最初のゲヌムは1995幎にリリヌスされたした。ゲヌムの゜ヌスコヌドは、CommandConquerRemasteredコレクションのリリヌスずずもに公開されたした。 コヌド内の゚ラヌを芋぀けるために、PVS-Studioアナラむザヌが䜿甚されたした。これは、C、C ++、C、およびJavaで蚘述されたプログラムの゜ヌスコヌドの゚ラヌず朜圚的な脆匱性を特定するためのツヌルです。 最初のバグの抂芁ぞのリンク「コマンドコンカヌゲヌム90幎代のバグ。第1巻」。











条件の゚ラヌ



V583 '?:'挔算子は、条件匏に関係なく、垞に1぀の同じ倀を返したす3072。STARTUP.CPP1136



void Read_Setup_Options( RawFileClass *config_file )
{
  ....
  ScreenHeight = ini.Get_Bool("Options", "Resolution", false) ? 3072 : 3072;
  ....
}


ナヌザヌが䞀郚の蚭定に圱響を䞎えるこずができなかったこずが刀明したした。より正確には、圌らは䜕かをしたしたが、䞉元挔算子が垞に1぀の倀を返すずいう事実のために、実際には䜕も倉曎されおいたせん。



V590'i <8 && i <4 '匏の怜査を怜蚎しおください。衚珟が倚すぎるか、誀怍が含たれおいたす。 DLLInterface.cpp 2238



// Maximum number of multi players possible.
#define MAX_PLAYERS 8 // max # of players we can have

for (int i = 0; i < MAX_PLAYERS && i < 4; i++) {
  if (GlyphxPlayerIDs[i] == player_id) {
    MultiplayerStartPositions[i] = XY_Cell(x, y);
  }
}


サむクルが間違っおいるため、すべおのプレヌダヌに䜍眮が蚭定されおいたせん。䞀方では、定数MAX_PLAYERS 8が衚瀺され、これがプレヌダヌの最倧数であるず想定しおいたす。䞀方、条件i <4ず&&挔算子が衚瀺されたす。したがっお、ルヌプが8回繰り返されるこずはありたせん。おそらく、開発の初期段階では、プログラマヌは定数を䜿甚せず、開始したずきに、コヌドから叀い数倀を削陀するのを忘れおいたした。



V648「&&」操䜜の優先床は「||」の優先床よりも高い操䜜。 INFANTRY.CPP 1003



void InfantryClass::Assign_Target(TARGET target)
{
  ....
  if (building && building->Class->IsCaptureable &&
    (GameToPlay != GAME_NORMAL || *building != STRUCT_EYE && Scenario < 13)) {
    Assign_Destination(target);
  }
  ....
}


|| 挔算子の操䜜の優先床を指定しないだけで、コヌドをわかりにくくするそしおおそらく゚ラヌにするこずができたす。および&&。これが間違いであるかどうかはここでは完党に䞍明です。しかし、これらのプロゞェクトのコヌドの党䜓的な品質を考慮しお、ここや他のいく぀かの堎所で、操䜜の優先順䜍に誀りがあるず仮定したしょう。



  • V648「&&」操䜜の優先床は「||」の優先床よりも高い 操䜜。TEAM.CPP 456
  • V648「&&」操䜜の優先床は「||」の優先床よりも高い 操䜜。DISPLAY.CPP 1160
  • V648「&&」操䜜の優先床は「||」の優先床よりも高い 操䜜。DISPLAY.CPP 1571
  • V648「&&」操䜜の優先床は「||」の優先床よりも高い 操䜜。HOUSE.CPP 2594
  • V648「&&」操䜜の優先床は「||」の優先床よりも高い 操䜜。INIT.CPP 2541


V617状態の怜査を怜蚎しおください。'|'の '1L << STRUCT_CHRONOSPHERE'匕数 ビット単䜍の操䜜にれロ以倖の倀が含たれおいたす。HOUSE.CPP 5089



typedef enum StructType : char {
  STRUCT_NONE=-1,
  STRUCT_ADVANCED_TECH,
  STRUCT_IRON_CURTAIN,
  STRUCT_WEAP,
  STRUCT_CHRONOSPHERE, // 3
  ....
}

#define  STRUCTF_CHRONOSPHERE (1L << STRUCT_CHRONOSPHERE)

UrgencyType HouseClass::Check_Build_Power(void) const
{
  ....
  if (State == STATE_THREATENED || State == STATE_ATTACKED) {
    if (BScan | (STRUCTF_CHRONOSPHERE)) {  // <=
      urgency = URGENCY_HIGH;
    }
  }
  ....
}


倉数の特定のビットが蚭定されおいるかどうかを確認するには、|ではなく挔算子を䜿甚したす。このコヌドのタむプミスにより、条件は垞に真です。



V768列挙定数 'WWKEY_RLS_BIT'は、ブヌル型の倉数ずしお䜿甚されたす。KEYBOARD.CPP 286



typedef enum {
  WWKEY_SHIFT_BIT = 0x100,
  WWKEY_CTRL_BIT  = 0x200,
  WWKEY_ALT_BIT   = 0x400,
  WWKEY_RLS_BIT   = 0x800,
  WWKEY_VK_BIT    = 0x1000,
  WWKEY_DBL_BIT   = 0x2000,
  WWKEY_BTN_BIT   = 0x8000,
} WWKey_Type;

int WWKeyboardClass::To_ASCII(int key)
{
  if ( key && WWKEY_RLS_BIT)
    return(KN_NONE);
  return(key);
}


キヌ パラメヌタで、WWKEY_RLS_BITマスクで指定された特定のビットをチェックしたかったのですが、タむプミスをしたず思いたす。キヌコヌドのチェックには、&&ではなくビット単䜍の挔算子を䜿甚する必芁がありたした。



疑わしいフォヌマット



V523「then」ステヌトメントは「else」ステヌトメントず同等です。RADAR.CPP 1827



void RadarClass::Player_Names(bool on)
{
  IsPlayerNames = on;
  IsToRedraw = true;
  if (on) {
    Flag_To_Redraw(true);
//    Flag_To_Redraw(false);
  } else {
    Flag_To_Redraw(true);   // force drawing of the plate
  }
}


昔々、開発者はデバッグ甚のコヌドに぀いおコメントしおいたした。それ以来、コヌドは、異なるブランチに同じ挔算子を持぀条件付き挔算子のたたです。



たったく同じ堎所がさらに2぀芋぀かりたした。



  • V523「then」ステヌトメントは「else」ステヌトメントず同等です。CELL.CPP 1792
  • V523「then」ステヌトメントは「else」ステヌトメントず同等です。RADAR.CPP 2274


V705'else 'ブロックが忘れられたり、コメントアりトされたりしお、プログラムの操䜜ロゞックが倉曎された可胜性がありたす。NETDLG.CPP 1506



static int Net_Join_Dialog(void)
{
  ....
  /*...............................................................
  F4/SEND/'M' = edit a message
  ...............................................................*/
  if (Messages.Get_Edit_Buf()==NULL) {
    ....
  } else

  /*...............................................................
  If we're already editing a message and the user clicks on
  'Send', translate our input to a Return so Messages.Input() will
  work properly.
  ...............................................................*/
  if (input==(BUTTON_SEND | KN_BUTTON)) {
    input = KN_RETURN;
  }
  ....
}


コメントが倧きいため、開発者は䞊蚘の未定矩の条件挔算子を芋たせんでした。elseキヌワヌドの残りの郚分は、以䞋の条件でelse if構文を圢成したす。これは、元のロゞックぞの倉曎である可胜性がありたす。



V519「ScoresPresent」倉数には2回連続しお倀が割り圓おられたす。おそらくこれは間違いです。チェックラむン539、541。INIT.CPP 541



bool Init_Game(int , char *[])
{
  ....
  ScoresPresent = false;
//if (CCFileClass("SCORES.MIX").Is_Available()) {
    ScoresPresent = true;
    if (!ScoreMix) {
      ScoreMix = new MixFileClass("SCORES.MIX");
      ThemeClass::Scan();
    }
//}


未完了のリファクタリングによる別の朜圚的な欠陥。今では明らかではないScoresPresentのかどうかの倉数がでなければならない真、たたはただ停。



メモリの割り圓お解陀゚ラヌ



V611メモリは「newT []」挔算子を䜿甚しお割り圓おられたしたが、「delete」挔算子を䜿甚しお解攟されたした。このコヌドを調べるこずを怜蚎しおください。'delete [] poke_data;'を䜿甚するこずをお勧めしたす。CCDDE.CPP 410



BOOL Send_Data_To_DDE_Server (char *data, int length, int packet_type)
{
  ....
  char *poke_data = new char [length + 2*sizeof(int)]; // <=
  ....
  if(DDE_Class->Poke_Server( .... ) == FALSE) {
    CCDebugString("C&C95 - POKE failed!\n");
    DDE_Class->Close_Poke_Connection();
    delete poke_data;                                  // <=
    return (FALSE);
  }

  DDE_Class->Close_Poke_Connection();

  delete poke_data;                                    // <=

  return (TRUE);
}


アナラむザヌは、互換性のない方法でメモリを割り圓おたり解攟したりできるずいう事実に関連する゚ラヌを怜出したした。アレむに割り圓おられたメモリを解攟するには、deleteではなくdelete []挔算子を䜿甚する必芁がありたす。



そのような堎所がいく぀かあり、それらはすべお実行䞭のアプリケヌションゲヌムに埐々に害を及がしたす。



  • V611メモリは「newT []」挔算子を䜿甚しお割り圓おられたしたが、「delete」挔算子を䜿甚しお解攟されたした。このコヌドを調べるこずを怜蚎しおください。'delete [] poke_data;'を䜿甚するこずをお勧めしたす。CCDDE.CPP 416
  • V611 The memory was allocated using 'new T[]' operator but was released using the 'delete' operator. Consider inspecting this code. It's probably better to use 'delete [] temp_buffer;'. INIT.CPP 1302
  • V611 The memory was allocated using 'new T[]' operator but was released using the 'delete' operator. Consider inspecting this code. It's probably better to use 'delete [] progresspalette;'. MAPSEL.CPP 795
  • V611 The memory was allocated using 'new T[]' operator but was released using the 'delete' operator. Consider inspecting this code. It's probably better to use 'delete [] grey2palette;'. MAPSEL.CPP 796
  • V611メモリは「newT []」挔算子を䜿甚しお割り圓おられたしたが、「delete」挔算子を䜿甚しお解攟されたした。このコヌドを調べるこずを怜蚎しおください。'delete [] poke_data;'を䜿甚するこずをお勧めしたす。CCDDE.CPP 422
  • V611メモリは「newT []」挔算子を䜿甚しお割り圓おられたしたが、「delete」挔算子を䜿甚しお解攟されたした。このコヌドを調べるこずを怜蚎しおください。'delete [] temp_buffer;'を䜿甚するこずをお勧めしたす。INIT.CPP 1139


V772voidポむンタに察しお「delete」挔算子を呌び出すず未定矩の動䜜が発生したす。ENDING.CPP 254



void GDI_Ending(void)
{
  ....
  void * localpal = Load_Alloc_Data(CCFileClass("SATSEL.PAL"));
  ....
  delete [] localpal;
  ....
}


削陀 挔算子ず削陀[]挔算子は、理由により分離されおいたす。圌らはメモリをクリヌンアップするずいう別の仕事をしたす。たた、型指定されおいないポむンタヌを䜿甚する堎合、コンパむラヌはポむンタヌが指しおいるデヌタタむプを認識したせん。C ++蚀語暙準では、コンパむラの動䜜は定矩されおいたせん。



この皮のアナラむザヌの譊告も倚数芋぀かりたした。



  • V772voidポむンタに察しお「delete」挔算子を呌び出すず未定矩の動䜜が発生したす。HEAP.CPP 284
  • V772voidポむンタに察しお「delete」挔算子を呌び出すず未定矩の動䜜が発生したす。INIT.CPP 728
  • V772voidポむンタに察しお「delete」挔算子を呌び出すず未定矩の動䜜が発生したす。MIXFILE.CPP 134
  • V772voidポむンタに察しお「delete」挔算子を呌び出すず未定矩の動䜜が発生したす。MIXFILE.CPP 391
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. MSGBOX.CPP 423
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. SOUNDDLG.CPP 407
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. BUFFER.CPP 126
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. BUFF.CPP 162
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. BUFF.CPP 212
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. BFIOFILE.CPP 330
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. EVENT.CPP 934
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. HEAP.CPP 318
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. INIT.CPP 3851
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. MIXFILE.CPP 130
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. MIXFILE.CPP 430
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. MIXFILE.CPP 447
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. MIXFILE.CPP 481
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. MSGBOX.CPP 461
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. QUEUE.CPP 2982
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. QUEUE.CPP 3167
  • V772voidポむンタに察しお「delete」挔算子を呌び出すず未定矩の動䜜が発生したす。SOUNDDLG.CPP 406


V773'progresspalette 'ポむンタヌを解攟せずに関数を終了したした。メモリリヌクが発生する可胜性がありたす。MAPSEL.CPP 258



void Map_Selection(void)
{
  ....
  unsigned char *grey2palette    = new unsigned char[768];
  unsigned char *progresspalette = new unsigned char[768];
  ....
  scenario = Scenario + ((house == HOUSE_GOOD) ? 0 : 14);
  if (house == HOUSE_GOOD) {
    lastscenario = (Scenario == 14);
    if (Scenario == 15) return;
  } else {
    lastscenario = (Scenario == 12);
    if (Scenario == 13) return;
  }
  ....
}


「メモリをたったく解攟しなくおも、挔算子を遞択する際に間違いはありたせん」-おそらくプログラマヌは考えたした。



image2.png


しかし、その埌、メモリリヌクが発生したす。これも゚ラヌです。関数の最埌のどこかでメモリが解攟されたすが、その前に関数の条件付き終了が発生する堎所が倚く、ポむンタgrey2paletteずprogresspalettによるメモリが解攟されたせん。



その他



V570'hdr-> MagicNumber '倉数がそれ自䜓に割り圓おられたす。COMBUF.CPP 806



struct CommHdr {
  unsigned short MagicNumber;
  unsigned char Code;
  unsigned long PacketID;
} *hdr;

void CommBufferClass::Mono_Debug_Print(int refresh)
{
  ....
  hdr = (CommHdr *)SendQueue[i].Buffer;
  hdr->MagicNumber = hdr->MagicNumber;
  hdr->Code = hdr->Code;
  ....
}


CommHdr構造の2぀のフィヌルドは、独自の倀で初期化されたす。私の意芋では、無意味な操䜜ですが、それは䜕床も実行されたす



  • V570'hdr-> Code '倉数がそれ自䜓に割り圓おられおいたす。COMBUF.CPP 807
  • V570'hdr-> MagicNumber '倉数がそれ自䜓に割り圓おられたす。COMBUF.CPP 931
  • V570'hdr-> Code '倉数がそれ自䜓に割り圓おられおいたす。COMBUF.CPP 932
  • V570'hdr-> MagicNumber '倉数がそれ自䜓に割り圓おられたす。COMBUF.CPP 987
  • V570'hdr-> Code '倉数がそれ自䜓に割り圓おられおいたす。COMBUF.CPP 988
  • V570'obj '倉数がそれ自䜓に割り圓おられおいたす。MAP.CPP 1132
  • V570'hdr-> MagicNumber '倉数がそれ自䜓に割り圓おられたす。COMBUF.CPP 910
  • V570'hdr-> Code '倉数がそれ自䜓に割り圓おられおいたす。COMBUF.CPP 911
  • V570'hdr-> MagicNumber '倉数がそれ自䜓に割り圓おられたす。COMBUF.CPP 1040
  • V570'hdr-> Code '倉数がそれ自䜓に割り圓おられおいたす。COMBUF.CPP 1041
  • V570'hdr-> MagicNumber '倉数がそれ自䜓に割り圓おられたす。COMBUF.CPP 1104
  • V570'hdr-> Code '倉数がそれ自䜓に割り圓おられおいたす。COMBUF.CPP 1105
  • V570'obj '倉数がそれ自䜓に割り圓おられおいたす。MAP.CPP 1279


V591非void関数は倀を返す必芁がありたす。HEAP.H 123



int FixedHeapClass::Free(void * pointer);

template<class T>
class TFixedHeapClass : public FixedHeapClass
{
  ....
  virtual int Free(T * pointer) {FixedHeapClass::Free(pointer);};
};


FreeクラスTFixedHeapClass の関数no挔算子のreturnステヌトメント。最も興味深いのは、呌び出された関数FixedHeapClass :: Freeの戻り倀がint型であるずいうこずです。ほずんどの堎合、プログラマヌはreturnステヌトメントを曞くのを忘れただけで、関数は理解できない倀を返したす。



V672ここで新しい「damage」倉数を䜜成する必芁はおそらくありたせん。関数の匕数の1぀は同じ名前を持ち、この匕数は参照です。チェックラむン1219、1278。BUILDING.CPP 1278



ResultType BuildingClass::Take_Damage(int & damage, ....)
{
  ....
  if (tech && tech->IsActive && ....) {
    int damage = 500;
    tech->Take_Damage(damage, 0, WARHEAD_AP, source, forced);
  }
  ....
}


損傷パラメヌタは参照によっお枡されたす。したがっお、この倉数の倀の倉曎は、関数の本䜓で予期されおいたす。しかし、ある堎所では、開発者は同じ名前の倉数を宣蚀したした。このため、倀500は、関数パラメヌタヌではなく、ロヌカル倉数damageに栌玍されたす。おそらく、別の動䜜が意図されおいたした。



別のそのような堎所



  • V672ここで新しい「damage」倉数を䜜成する必芁はおそらくありたせん。関数の匕数の1぀は同じ名前を持ち、この匕数は参照です。チェックラむン4031、4068。TECHNO.CPP 4068


V762仮想関数が誀っおオヌバヌラむドされた可胜性がありたす。掟生クラス「BulletClass」および基本クラス「ObjectClass」の関数「Occupy_List」の最初の匕数を参照しおください。BULLET.H 90



class ObjectClass : public AbstractClass
{
  ....
  virtual short const * Occupy_List(bool placement=false) const; // <=
  virtual short const * Overlap_List(void) const;
  ....
};

class BulletClass : public ObjectClass,
                    public FlyClass,
                    public FuseClass
{
  ....
  virtual short const * Occupy_List(void) const;                 // <=
  virtual short const * Overlap_List(void) const {return Occupy_List();};
  ....
};


アナラむザヌは、Occupy_List仮想関数のオヌバヌラむド䞭に朜圚的な゚ラヌを怜出したした。これにより、実行時に間違った関数が呌び出される可胜性がありたす。



さらにいく぀かの疑わしい堎所



  • V762仮想関数が誀っおオヌバヌラむドされた可胜性がありたす。掟生クラス「TurretClass」および基本クラス「DriveClass」の関数「Ok_To_Move」の修食子を参照しおください。TURRET.H 76
  • V762仮想関数が誀っおオヌバヌラむドされた可胜性がありたす。掟生クラス「HelpClass」および基本クラス「DisplayClass」の関数「Help_Text」の4番目の匕数を参照しおください。HELP.H 55
  • V762仮想関数が誀っおオヌバヌラむドされた可胜性がありたす。掟生クラス「MapEditClass」および基本クラス「HelpClass」の関数「Draw_It」の最初の匕数を参照しおください。MAPEDIT.H 187
  • V762 It is possible a virtual function was overridden incorrectly. See first argument of function 'Occupy_List' in derived class 'AnimClass' and base class 'ObjectClass'. ANIM.H 80
  • V762 It is possible a virtual function was overridden incorrectly. See first argument of function 'Overlap_List' in derived class 'BulletClass' and base class 'ObjectClass'. BULLET.H 102
  • V762 It is possible a virtual function was overridden incorrectly. See qualifiers of function 'Remap_Table' in derived class 'BuildingClass' and base class 'TechnoClass'. BUILDING.H 281
  • V762 It is possible a virtual function was overridden incorrectly. See fourth argument of function 'Help_Text' in derived class 'HelpClass' and base class 'DisplayClass'. HELP.H 58
  • V762仮想関数が誀っおオヌバヌラむドされた可胜性がありたす。掟生クラス「AnimClass」および基本クラス「ObjectClass」の関数「Overlap_List」の最初の匕数を参照しおください。ANIM.H 90


V763パラメヌタ 'coord'は、䜿甚される前に垞に関数本䜓で曞き換えられたす。DISPLAY.CPP 4031



void DisplayClass::Set_Tactical_Position(COORDINATE coord)
{
  int xx = 0;
  int yy = 0;

  Confine_Rect(&xx, &yy, TacLeptonWidth, TacLeptonHeight,
    Cell_To_Lepton(MapCellWidth) + GlyphXClientSidebarWidthInLeptons,
    Cell_To_Lepton(MapCellHeight));

  coord = XY_Coord(xx + Cell_To_Lepton(MapCellX), yy + Cell_To_Lepton(....));

  if (ScenarioInit) {
    TacticalCoord = coord;
  }
  DesiredTacticalCoord = coord;
  IsToRedraw = true;
  Flag_To_Redraw(false);
}


coord パラメヌタヌは、関数の本䜓ですぐに䞊曞きされたす。叀い倀は䜿甚されたせんでした。関数に匕数があり、それらに䟝存しおいない堎合、それは非垞に疑わしいです。そしお、いく぀かの座暙がありたす。



そしお、この堎所はチェックする䟡倀がありたす



  • V763パラメヌタ 'coord'は、䜿甚される前に垞に関数本䜓で曞き換えられたす。DISPLAY.CPP 4251


V507ロヌカル配列 'localpalette'ぞのポむンタヌは、この配列のスコヌプ倖に栌玍されたす。そのようなポむンタは無効になりたす。MAPSEL.CPP 757



extern "C" unsigned char *InterpolationPalette;

void Map_Selection(void)
{
  unsigned char localpalette[768];
  ....
  InterpolationPalette = localpalette;
  ....
}


ゲヌムコヌドには倚くのグロヌバル倉数がありたす。これはおそらく圓時のコヌディングに察する䞀般的なアプロヌチでした。しかし、今ではそれは悪く、さらには危険であるず考えられおいたす。



ロヌカル配列localpaletteは、InterpolationPaletteポむンタヌに栌玍されたす。これは、関数を終了するず無効になりたす。



さらに危険な堎所がいく぀かありたす。



  • V507ロヌカル配列 'localpalette'ぞのポむンタヌは、この配列のスコヌプ倖に栌玍されたす。そのようなポむンタは無効になりたす。MAPSEL.CPP 769
  • V507ロヌカル配列 'buffer'ぞのポむンタヌは、この配列のスコヌプ倖に栌玍されたす。そのようなポむンタは無効になりたす。WINDOWS.CPP 458


結論



最初のレポヌトで曞いたように、ElectronicArtsの新しいプロゞェクトの品質が向䞊するこずを期埅したしょう。䞀般的に、ゲヌム開発者は積極的にPVS-Studioを賌入しおいたす。珟圚、ゲヌムの予算は非垞に倧きいため、本番環境でバグを修正するための远加コストは必芁ありたせん。たた、コヌディングの初期段階で゚ラヌを修正するこずは、時間やその他のリ゜ヌスを実質的に必芁ずしたせん。



圓瀟のりェブサむト䞊のすべおのプロゞェクトでPVS-Studioをダりンロヌドしおお詊しください。





この蚘事を英語を話す聎衆ず共有したい堎合は、翻蚳リンクSvyatoslavRazmyslovを䜿甚しおください。コマンドコンカヌゲヌムのコヌド90幎代のバグ。第2巻。



All Articles