Pushen einer Struktur in ein Array, das selbst ein Array enthält

Ich habe diesen Artikel gelesen , der ein Beispiel für einen Vertrag gibt, der nicht funktioniert. Obwohl sie es in dem Artikel behoben haben, wollte ich wissen, warum es nicht funktioniert. Ich habe eine vereinfachte Version unten kopiert:

pragma solidity ^0.4.18;

contract StructArrayInitWrong {
  struct Room {
    address[] players;       
  }  
  Room[] rooms;

  function createRoom() public {
    address[] adr;
    adr.push(msg.sender);
    Room memory room = Room(adr);   
    rooms.push(room);
  }

  function getRoomsLength() view returns (uint) {
    return rooms.length;
  }
}

Das Seltsame ist, dass die roomsArray-Länge jedes Mal um zwei zunimmt, wenn createRoomdieser Vertrag aufgerufen wird. Können Sie sich dieses Verhalten erklären? Vielen Dank.

Antworten (1)

Das Problem liegt in dieser Zeile

address[] adr;

Es gibt eine "Warnung" vor nicht initialisiertem Speicher. Hier geht mehr vor, als eine sanfte Warnung vermuten lässt. Ich verstehe wirklich nicht, warum dies kein schwerer Fehler ist, sodass der Entwickler ihn nicht akzeptiert.

Da der Speicher nicht initialisiert war, wusste der Compiler nicht, wo er das dynamische Array ablegen sollte, adr[]also legte er es in den ersten Steckplatz . Sie haben richtig gelesen. Es stampft weiter rooms[]. Autsch!

Da der Besetzer dieses Slots zufällig auch ein dynamisches Array ist, verwendet es auch das erste Wort, um die Array-Länge zu beschreiben. So erhalten wir das seltsame beobachtete Verhalten ... gehen Sie zu einem Array über, dann weiter zu dem anderen und schwups! - beide Arrays haben die Länge 2.

Dies ist nicht der einzige Fall, in dem Speicher, der innerhalb von Funktionen statt an der üblichen Stelle (außerhalb) deklariert wird, potenziell katastrophale Datenüberschreibungen verursacht. Siehe hier für ein weiteres Beispiel: Solc Compiler oversight? Ungeeignete Mapping-Deklaration überschreibt Speicher

Wenn man bedenkt, dass Smart Contracts klar und fehlerfrei sein sollen, bin ich kein großer Fan von solchen unbeabsichtigten Ergebnissen. Das und Referenzgrößen summieren sich für meinen Geschmack zu etwas zu viel Voodoo.

Vielleicht meldet sich jemand anderes mit besseren Heuristiken. Ich könnte vorschlagen, zwei Gewohnheiten zu bilden

  • Ordnen Sie Zustandsvariablen immer an der gleichen Stelle, nahe dem oberen Rand, außerhalb von Funktionen an, um diese Art von unerwartetem Überschreiben zu vermeiden.
  • Stellen Sie niemals eine Variable ein, die zuvor aus dem indizierten Speicher gelesen wurde, da dies den Speicher unerwartet überschreiben könnte. Siehe hier: http://vessenes.com/solidity-frustrations-references-and-mapping/

Hier ist der Code mit einer Änderung und funktioniert wie erwartet:

pragma solidity ^0.4.18;

contract StructArrayInitWrong {

  struct Room {
    address[] players;       
  }  
  Room[] rooms;
  address[] adr; // <=== if we're going to store this, then let's store this.

  function createRoom() public {
                                    // <=== note gaping hole
    adr.push(msg.sender);
    Room memory room = Room(adr);   
    rooms.push(room);
  }

  function getRoomsLength() public view returns (uint) {
    return rooms.length;
  }
}

Ich hoffe es hilft.

Danke für die ausführliche Antwort! Ich wollte das Array nicht im Speicher speichern, also habe ich die Variable memorywie folgt geändert: address[] memory adr = new address[](1); adr[0] = msg.sender;. Dies scheint auch zu funktionieren.