Command & Conquer Spielcode: Bugs aus den 90ern. Band zwei

image1.png


Das amerikanische Unternehmen Electronic Arts Inc (EA) hat den Open-Source-Code der Spiele Command & Conquer: Tiberian Dawn und Command & Conquer: Red Alert veröffentlicht. Mit dem PVS-Studio-Analysegerät wurden mehrere Dutzend Fehler im Quellcode gefunden. Bitte begrüßen Sie die Fortsetzung der Beschreibung der gefundenen Fehler.



Einführung



Command & Conquer ist eine Reihe von Computerspielen im Echtzeit-Strategie-Genre. Das erste Spiel der Serie wurde 1995 veröffentlicht. Der Quellcode der Spiele wurde veröffentlicht mit der Veröffentlichung der Command & Conquer Remastered Sammlung .



Um Fehler im Code zu finden, wurde der PVS-Studio- Analysator verwendet . Es ist ein Tool zum Identifizieren von Fehlern und potenziellen Schwachstellen im Quellcode von Programmen, die in C, C ++, C # und Java geschrieben wurden.



Link zur ersten Fehlerübersicht: " Command & Conquer Game : Fehler aus den 90ern. Band Eins ".



Fehler in den Bedingungen



V583 Der Operator '?:' Gibt unabhängig von seinem bedingten Ausdruck immer ein und denselben Wert zurück: 3072. STARTUP.CPP 1136



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


Es stellt sich heraus, dass Benutzer einige Einstellungen nicht beeinflussen konnten. Genauer gesagt haben sie etwas getan, aber aufgrund der Tatsache, dass der ternäre Operator immer einen Wert zurückgibt, hat sich tatsächlich nichts geändert.



V590 Überprüfen Sie den Ausdruck 'i <8 && i <4'. Der Ausdruck ist übertrieben oder enthält einen Druckfehler. 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);
  }
}


Aufgrund des falschen Zyklus ist die Position nicht für alle Spieler festgelegt. Einerseits sehen wir die Konstante MAX_PLAYERS 8 und nehmen an, dass dies die maximale Anzahl von Spielern ist. Andererseits sehen wir die Bedingung i <4 und den Operator && . Somit macht die Schleife niemals 8 Iterationen. Höchstwahrscheinlich verwendete der Programmierer in der Anfangsphase der Entwicklung keine Konstanten, und als er anfing, vergaß er, die alten Zahlen aus dem Code zu entfernen.



V648 Die Priorität der Operation '&&' ist höher als die der Operation '||' Betrieb. 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);
  }
  ....
}


Sie können den Code nicht offensichtlich (und höchstwahrscheinlich fehlerhaft) machen, indem Sie einfach die Priorität der Operationen für die Operatoren || nicht angeben. und && . Es ist hier völlig unklar, ob dies ein Fehler ist oder nicht. Angesichts der Gesamtqualität des Codes dieser Projekte nehmen wir jedoch an, dass hier und an mehreren anderen Stellen Fehler bei der Priorität der Operationen auftreten:



  • V648 Die Priorität der Operation '&&' ist höher als die der Operation '||' Betrieb. TEAM.CPP 456
  • V648 Die Priorität der Operation '&&' ist höher als die der Operation '||' Betrieb. DISPLAY.CPP 1160
  • V648 Die Priorität der Operation '&&' ist höher als die der Operation '||' Betrieb. DISPLAY.CPP 1571
  • V648 Die Priorität der Operation '&&' ist höher als die der Operation '||' Betrieb. HOUSE.CPP 2594
  • V648 Die Priorität der Operation '&&' ist höher als die der Operation '||' Betrieb. INIT.CPP 2541


V617 Überprüfen Sie den Zustand. Das Argument '((1L << STRUCT_CHRONOSPHERE))' des '|' Die bitweise Operation enthält einen Wert ungleich Null. 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;
    }
  }
  ....
}


Verwenden Sie den Operator & und nicht |, um zu überprüfen, ob bestimmte Bits in einer Variablen gesetzt sind. Aufgrund eines Tippfehlers in diesem Code ist die Bedingung immer wahr.



V768 Die Aufzählungskonstante 'WWKEY_RLS_BIT' wird als Variable eines Booleschen Typs verwendet. 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);
}


Ich denke, im Schlüsselparameter wollten sie ein bestimmtes Bit überprüfen, das von der WWKEY_RLS_BIT- Maske angegeben wurde, machten aber einen Tippfehler. Der bitweise Operator & anstelle von && sollte verwendet worden sein, um den Schlüsselcode zu überprüfen.



Verdächtige Formatierung



V523 Die Anweisung 'then' entspricht der Anweisung '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
  }
}


Es war einmal ein Entwickler, der Code zum Debuggen kommentierte. Seitdem ist der Code ein bedingter Operator mit denselben Operatoren in verschiedenen Zweigen geblieben.



Genau die gleichen Orte wurden zwei weitere gefunden:



  • V523 Die Anweisung 'then' entspricht der Anweisung 'else'. CELL.CPP 1792
  • V523 Die Anweisung 'then' entspricht der Anweisung 'else'. RADAR.CPP 2274


V705 Es ist möglich, dass der Block 'else' vergessen oder auskommentiert wurde, wodurch die Betriebslogik des Programms geändert wurde. 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;
  }
  ....
}


Aufgrund eines großen Kommentars hat der Entwickler den obigen undefinierten bedingten Operator nicht gesehen. Der Rest des else- Schlüsselworts wird mit der Bedingung unterhalb der else if-Konstruktion gebildet , was höchstwahrscheinlich eine Änderung der ursprünglichen Logik darstellt.



V519 Der Variablen 'ScoresPresent' werden zweimal nacheinander Werte zugewiesen. Vielleicht ist das ein Fehler. Überprüfen Sie die Zeilen: 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();
    }
//}


Ein weiterer möglicher Defekt aufgrund unvollendeter Umgestaltung. Nun ist es nicht klar , ob die ScoresPresent Variable sein muss , wahr oder noch falsch .



Speicherfreigabefehler



V611 Der Speicher wurde mit dem Operator 'new T []' zugewiesen, aber mit dem Operator 'delete' freigegeben. Überprüfen Sie diesen Code. Es ist wahrscheinlich besser, 'delete [] poke_data;' zu verwenden. 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);
}


Der Analysator hat einen Fehler festgestellt, der darauf zurückzuführen ist, dass Speicher auf inkompatible Weise zugewiesen und freigegeben werden kann. Um den für das Array zugewiesenen Speicher freizugeben, sollten Sie den Operator delete [] und nicht delete verwendet haben .



Es gab mehrere solcher Orte, und alle schaden nach und nach der laufenden Anwendung (dem Spiel):



  • V611 Der Speicher wurde mit dem Operator 'new T []' zugewiesen, aber mit dem Operator 'delete' freigegeben. Überprüfen Sie diesen Code. Es ist wahrscheinlich besser, 'delete [] poke_data;' zu verwenden. 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 Der Speicher wurde mit dem Operator 'new T []' zugewiesen, aber mit dem Operator 'delete' freigegeben. Überprüfen Sie diesen Code. Es ist wahrscheinlich besser, 'delete [] poke_data;' zu verwenden. CCDDE.CPP 422
  • V611 Der Speicher wurde mit dem Operator 'new T []' zugewiesen, aber mit dem Operator 'delete' freigegeben. Überprüfen Sie diesen Code. Es ist wahrscheinlich besser, 'delete [] temp_buffer;' zu verwenden. INIT.CPP 1139


V772 Das Aufrufen eines Löschoperators für einen ungültigen Zeiger führt zu undefiniertem Verhalten. ENDING.CPP 254



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


Die Operatoren delete und delete [] sind aus einem bestimmten Grund getrennt. Sie bereinigen das Gedächtnis auf andere Weise. Und wenn ein untypisierter Zeiger verwendet wird, weiß der Compiler nicht, auf welchen Datentyp der Zeiger zeigt. Im C ++ - Sprachstandard ist das Verhalten des Compilers undefiniert.



Es wurden auch eine Reihe von Warnungen des Analysators dieser Art gefunden:



  • V772 Das Aufrufen eines Löschoperators für einen ungültigen Zeiger führt zu undefiniertem Verhalten. HEAP.CPP 284
  • V772 Das Aufrufen eines Löschoperators für einen ungültigen Zeiger führt zu undefiniertem Verhalten. INIT.CPP 728
  • V772 Das Aufrufen eines Löschoperators für einen ungültigen Zeiger führt zu undefiniertem Verhalten. MIXFILE.CPP 134
  • V772 Das Aufrufen eines Löschoperators für einen ungültigen Zeiger führt zu undefiniertem Verhalten. 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
  • V772 Das Aufrufen eines Löschoperators für einen ungültigen Zeiger führt zu undefiniertem Verhalten. SOUNDDLG.CPP 406


V773 Die Funktion wurde beendet, ohne den Zeiger 'progresspalette' loszulassen. Ein Speicherverlust ist möglich. 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;
  }
  ....
}


"Wenn Sie den Speicher überhaupt nicht freigeben, werde ich mich definitiv nicht irren, wenn ich einen Operator auswähle!" - Vielleicht dachte der Programmierer.



image2.png


Dann tritt jedoch ein Speicherverlust auf, der ebenfalls ein Fehler ist. Irgendwo am Ende der Funktion wird Speicher freigegeben, aber vorher gibt es viele Stellen, an denen der bedingte Austritt aus der Funktion erfolgt und der Speicher durch die Zeiger grey2palette und progresspalett nicht freigegeben wird.



Verschiedenes



V570 Die Variable 'hdr-> MagicNumber' wird sich selbst zugewiesen. 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;
  ....
}


Zwei Felder der CommHdr-Struktur werden mit ihren eigenen Werten initialisiert. Meiner Meinung nach eine sinnlose Operation, die aber oft durchgeführt wird:



  • V570 Die Variable 'hdr-> Code' wird sich selbst zugewiesen. COMBUF.CPP 807
  • V570 Die Variable 'hdr-> MagicNumber' wird sich selbst zugewiesen. COMBUF.CPP 931
  • V570 Die Variable 'hdr-> Code' wird sich selbst zugewiesen. COMBUF.CPP 932
  • V570 Die Variable 'hdr-> MagicNumber' wird sich selbst zugewiesen. COMBUF.CPP 987
  • V570 Die Variable 'hdr-> Code' wird sich selbst zugewiesen. COMBUF.CPP 988
  • V570 Die Variable 'obj' wird sich selbst zugewiesen. MAP.CPP 1132
  • V570 Die Variable 'hdr-> MagicNumber' wird sich selbst zugewiesen. COMBUF.CPP 910
  • V570 Die Variable 'hdr-> Code' wird sich selbst zugewiesen. COMBUF.CPP 911
  • V570 Die Variable 'hdr-> MagicNumber' wird sich selbst zugewiesen. COMBUF.CPP 1040
  • V570 Die Variable 'hdr-> Code' wird sich selbst zugewiesen. COMBUF.CPP 1041
  • V570 Die Variable 'hdr-> MagicNumber' wird sich selbst zugewiesen. COMBUF.CPP 1104
  • V570 Die Variable 'hdr-> Code' wird sich selbst zugewiesen. COMBUF.CPP 1105
  • V570 Die Variable 'obj' wird sich selbst zugewiesen. MAP.CPP 1279


V591 Non-void-Funktion sollte einen Wert zurückgeben. HEAP.H 123



int FixedHeapClass::Free(void * pointer);

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


Die Funktionen der Free- Klasse TFixedHeapClass no-Operator- Return-Anweisung . Das Interessanteste ist, dass die aufgerufene Funktion FixedHeapClass :: Free auch einen Rückgabewert vom Typ int hat . Höchstwahrscheinlich hat der Programmierer einfach vergessen, die return-Anweisung zu schreiben , und jetzt gibt die Funktion einen unverständlichen Wert zurück.



V672 Es ist wahrscheinlich nicht erforderlich, die neue Variable "Schaden" hier zu erstellen. Eines der Argumente der Funktion hat denselben Namen und dieses Argument ist eine Referenz. Überprüfen Sie die Zeilen: 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);
  }
  ....
}


Der Schadensparameter wird als Referenz übergeben. Daher werden Änderungen der Werte dieser Variablen im Hauptteil der Funktion erwartet. An einer Stelle hat der Entwickler jedoch eine gleichnamige Variable deklariert. Aus diesem Grund wird der Wert 500 in der lokalen Variablen Damage und nicht in einem Funktionsparameter gespeichert. Vielleicht war ein anderes Verhalten beabsichtigt.



Ein anderer solcher Ort:



  • V672 Es ist wahrscheinlich nicht erforderlich, die neue Variable "Schaden" hier zu erstellen. Eines der Argumente der Funktion hat denselben Namen und dieses Argument ist eine Referenz. Überprüfen Sie die Zeilen: 4031, 4068. TECHNO.CPP 4068


V762 Möglicherweise wurde eine virtuelle Funktion falsch überschrieben. Siehe erstes Argument der Funktion 'Occupy_List' in der abgeleiteten Klasse 'BulletClass' und der Basisklasse 'ObjectClass'. 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();};
  ....
};


Der Analysator hat beim Überschreiben der virtuellen Funktion Occupy_List einen potenziellen Fehler festgestellt . Dies kann dazu führen, dass zur Laufzeit falsche Funktionen aufgerufen werden.



Noch ein paar verdächtige Orte:



  • V762 Möglicherweise wurde eine virtuelle Funktion falsch überschrieben. Siehe Qualifikationsmerkmale der Funktion 'Ok_To_Move' in der abgeleiteten Klasse 'TurretClass' und der Basisklasse 'DriveClass'. TURRET.H 76
  • V762 Möglicherweise wurde eine virtuelle Funktion falsch überschrieben. Siehe viertes Argument der Funktion 'Help_Text' in der abgeleiteten Klasse 'HelpClass' und der Basisklasse 'DisplayClass'. HELP.H 55
  • V762 Möglicherweise wurde eine virtuelle Funktion falsch überschrieben. Siehe erstes Argument der Funktion 'Draw_It' in der abgeleiteten Klasse 'MapEditClass' und der Basisklasse 'HelpClass'. 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 Möglicherweise wurde eine virtuelle Funktion falsch überschrieben. Siehe erstes Argument der Funktion 'Overlap_List' in der abgeleiteten Klasse 'AnimClass' und der Basisklasse 'ObjectClass'. ANIM.H 90


V763 Der Parameter 'coord' wird vor der Verwendung immer im Funktionskörper neu geschrieben. 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);
}


Der Koordinatenparameter wird im Hauptteil der Funktion sofort überschrieben. Der alte Wert wurde nicht verwendet. Es ist sehr verdächtig, wenn eine Funktion Argumente enthält und nicht von diesen abhängt. Und dann gibt es einige Koordinaten.



Und dieser Ort ist einen Besuch wert:



  • V763 Der Parameter 'coord' wird vor der Verwendung immer im Funktionskörper neu geschrieben. DISPLAY.CPP 4251


V507 Der Zeiger auf das lokale Array 'localpalette' wird außerhalb des Bereichs dieses Arrays gespeichert. Ein solcher Zeiger wird ungültig. MAPSEL.CPP 757



extern "C" unsigned char *InterpolationPalette;

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


Der Spielcode enthält viele globale Variablen. Dies war damals wahrscheinlich ein gängiger Ansatz für die Codierung. Aber jetzt gilt es als schlecht und sogar gefährlich.



Die lokale Array-Localpalette wird im InterpolationPalette-Zeiger gespeichert, der nach dem Beenden der Funktion ungültig wird.



Ein paar gefährlichere Orte:



  • V507 Der Zeiger auf das lokale Array 'localpalette' wird außerhalb des Bereichs dieses Arrays gespeichert. Ein solcher Zeiger wird ungültig. MAPSEL.CPP 769
  • V507 Der Zeiger auf den 'Puffer' des lokalen Arrays wird außerhalb des Bereichs dieses Arrays gespeichert. Ein solcher Zeiger wird ungültig. WINDOWS.CPP 458


Fazit



Hoffen wir, wie ich im ersten Bericht schrieb, dass die neuen Projekte von Electronic Arts von besserer Qualität sind. Im Allgemeinen kaufen Spieleentwickler PVS-Studio aktiv. Jetzt sind die Budgets für Spiele ziemlich groß, sodass niemand die zusätzlichen Kosten für die Behebung von Fehlern in der Produktion benötigt. Das Beheben eines Fehlers in einem frühen Stadium der Codierung erfordert praktisch keine Zeit und andere Ressourcen.



Wir laden Sie ein, PVS-Studio für alle Projekte auf unserer Website herunterzuladen und zu testen.





Wenn Sie diesen Artikel einem englischsprachigen Publikum zugänglich machen möchten, verwenden Sie bitte den Übersetzungslink: Svyatoslav Razmyslov. Der Code des Command & Conquer-Spiels: Bugs aus den 90ern. Band zwei .



All Articles