Freigeben über


Programmiergrundlagen

Es wird empfohlen, dass Ihr Anwendungscode einen Mindestqualitätsstandard erfüllt, der in diesem Thema definiert ist. Durch unsere Partnerschaften mit Kunden, die ihre in der Produktion bereitgestellten Anwendungen verbessern möchten, haben wir einige häufige Probleme gefunden, die die Leistung von Anwendungen verbessern, wenn sie behoben wurden.

Häufige Probleme

  • Beim Festlegen des Ziel-API-Satzes wird empfohlen, die neuesten CMake- und Azure Sphere-Tools zu verwenden und letztendlich die endgültigen Releasebinärdateien zu kompilieren, indem Sie festlegen AZURE_SPHERE_TARGET_API_SET="latest-lts". Weitere Informationen finden Sie unter Codierung für die Sicherheit nach erneuerbaren Quellen.

Hinweis

Legen Sie beim Erstellen von Imagepaketen, die innerhalb eines Fertigungsprozesses quergeladen werden sollen, auf die entsprechende Azure Sphere-Betriebssystemversion fest AZURE_SPHERE_TARGET_API_SET , mit der das Gerät bezogen oder wiederhergestellt wurde. Andernfalls wird das Imagepaket vom Azure Sphere-Betriebssystem abgelehnt.

  • Wenn Sie bereit sind , eine Anwendung in der Produktion bereitzustellen , stellen Sie sicher, dass Sie die endgültigen Imagepakete im Releasemodus kompilieren.
  • Es ist üblich, dass Anwendungen trotz Compilerwarnungen in der Produktion bereitgestellt werden. Durch das Erzwingen einer Nullwarnungsrichtlinie für vollständige Builds wird sichergestellt, dass jede Compilerwarnung absichtlich behandelt wird. Im Folgenden sind die häufigsten Warnungstypen aufgeführt, die dringend empfohlen werden:
    • Implizite Konvertierungswarnungen: Fehler werden häufig aufgrund von impliziten Konvertierungen eingeführt, die sich aus anfänglichen, schnellen Implementierungen ergeben, die nicht überprüft wurden. Code mit vielen impliziten numerischen Konvertierungen zwischen verschiedenen numerischen Typen kann beispielsweise zu einem Kritischen Genauigkeitsverlust oder sogar zu Berechnungs- oder Verzweigungsfehlern führen. Für die ordnungsgemäße Anpassung aller numerischen Typen wird sowohl die beabsichtigte Analyse als auch die Umwandlung empfohlen, nicht nur die Umwandlung.
    • Vermeiden Sie es, die erwarteten Parametertypen zu ändern: Wenn beim Aufrufen von APIs keine explizite Umwandlung erfolgt, können implizite Konvertierungen Probleme verursachen. Beispiel: Überlauf eines Puffers, wenn anstelle eines numerischen Typs ohne Vorzeichen ein numerischer Typ mit Vorzeichen verwendet wird.
    • Const-Discarding-Warnungen: Wenn eine Funktion einen const-Typ als Parameter erfordert, kann das Überschreiben zu Fehlern und unvorhersehbarem Verhalten führen. Der Grund für die Warnung besteht darin, sicherzustellen, dass der parameter const intakt bleibt und die Einschränkungen beim Entwerfen einer bestimmten API oder Funktion berücksichtigt.
    • Inkompatible Zeiger- oder Parameterwarnungen: Das Ignorieren dieser Warnung kann häufig Fehler verbergen, die später schwer zu verfolgen sind. Das Entfernen dieser Warnungen kann dazu beitragen, die Diagnosezeit anderer Anwendungsprobleme zu verkürzen.
  • Das Einrichten einer konsistenten CI/CD-Pipeline ist der Schlüssel für eine nachhaltige, langfristige Anwendungsverwaltung, da sie die einfache Neuerstellung von Binärdateien und deren entsprechenden Symbolen zum Debuggen älterer Anwendungsreleases ermöglicht. Eine geeignete Verzweigungsstrategie ist auch für die Nachverfolgung von Releases unerlässlich und vermeidet kostspieligen Speicherplatz beim Speichern von Binärdaten.
  • Definieren Sie nach Möglichkeit alle gängigen festen Zeichenfolgen als global const char* , anstatt sie hart zu codieren (z. B. innerhalb von printf Befehlen), damit sie als Datenzeiger in der gesamten Codebasis verwendet werden können, während der Code besser verwaltbar bleibt. In realen Anwendungen hat das Sammeln von allgemeinem Text aus Protokollen oder Zeichenfolgenbearbeitungen (z OK. B. , Succeededoder JSON-Eigenschaftsnamen) und seine Globalisierung in Konstanten häufig zu Einsparungen im schreibgeschützten Datenspeicherabschnitt (auch als RODATA bezeichnet) geführt, was zu Einsparungen im Flashspeicher führt, der in anderen Abschnitten (z. B. .text für mehr Code) verwendet werden könnte. Dieses Szenario wird häufig übersehen, kann aber zu erheblichen Einsparungen beim Flashspeicher führen.

Hinweis

Dies kann auch einfach durch Aktivieren von Compileroptimierungen (z -fmerge-constants . B. auf gcc) erreicht werden. Wenn Sie sich für diesen Ansatz entscheiden, überprüfen Sie auch die Compilerausgabe, und überprüfen Sie, ob die gewünschten Optimierungen angewendet wurden, da diese je nach Compilerversion variieren können.

  • Erwägen Sie bei globalen Datenstrukturen nach Möglichkeit, relativ kleinen Arraymembern feste Längen zuzuweisen, anstatt Zeiger auf dynamisch zugeordneten Speicher zu verwenden. Zum Beispiel:
    typedef struct {
      int chID;
      ...
      char chName[SIZEOF_CHANNEL_NAME]; // This approach is preferable, and easier to use e.g. in a function stack.
      char *chName; // Unless this points to a constant, tracking a memory buffer introduces more complexity, to be weighed with the cost/benefit, especially when using multiple instances of the structure.
      ...
    } myConfig;
  • Vermeiden Sie nach Möglichkeit die dynamische Speicherbelegung, insbesondere innerhalb häufig aufgerufener Funktionen.
  • Suchen Sie in C nach Funktionen, die einen Zeiger auf einen Speicherpuffer zurückgeben, und konvertieren Sie sie in Funktionen, die einen referenzierten Pufferzeiger und seine zugehörige Größe an die Aufrufer zurückgeben. Der Grund dafür ist, dass die Rückgabe nur eines Zeigers auf einen Puffer häufig zu Problemen mit dem aufrufenden Code geführt hat, da die Größe des zurückgegebenen Puffers nicht zwangsweise bestätigt wird und daher die Konsistenz des Heaps gefährden könnte. Zum Beispiel:
    // This approach is preferable:
    MY_RESULT_TYPE getBuffer(void **ptr, size_t &size, [...other parameters..])
    
    // This should be avoided, as it lacks tracking the size of the returned buffer and a dedicated result code:
    void *getBuffer([...other parameters..])

Dynamische Container und Puffer

Container wie Listen und Vektoren werden auch häufig in eingebetteten C-Anwendungen verwendet, mit dem Vorbehalt, dass sie aufgrund von Speicherbeschränkungen bei der Verwendung von Standardbibliotheken in der Regel explizit codiert oder als Bibliotheken verknüpft werden müssen. Diese Bibliotheksimplementierungen können eine intensive Speicherauslastung auslösen, wenn sie nicht sorgfältig entworfen wurden.

Neben den typischen statisch zugeordneten Arrays oder implementierungen mit hoher Speicherdynamik empfehlen wir einen ansatz für die inkrementelle Zuordnung. Beginnen Sie beispielsweise mit einer leeren Warteschlangenimplementierung von N vorab zugewiesenen Objekten. Beim (N+1)ten Warteschlangenpush wächst die Warteschlange um ein festes X zusätzliches vorab zugewiesenes Objekt (N=N+X), das dynamisch zugeordnet bleibt, bis eine weitere Ergänzung der Warteschlange die aktuelle Kapazität überläuft und die Speicherbelegung um X zusätzliche vorab zugewiesene Objekte erhöht. Sie können schließlich eine neue Komprimierungsfunktion implementieren, um sparsam aufzurufen (da es zu teuer wäre, regelmäßig aufzurufen), um nicht verwendeten Speicher freizugeben.

Ein dedizierter Index behält die Anzahl der aktiven Objekte für die Warteschlange dynamisch bei, die für zusätzlichen Überlaufschutz auf einen Maximalwert begrenzt werden kann.

Dieser Ansatz beseitigt das "Chatter", das durch kontinuierliche Speicherbelegung und Aufhebung der Zuordnung in herkömmlichen Warteschlangenimplementierungen generiert wird. Weitere Informationen finden Sie unter Speicherverwaltung und -nutzung. Sie können ähnliche Ansätze für Strukturen wie Listen, Arrays usw. implementieren.