Sdílet prostřednictvím


Základy kódování

Důležité

Toto je dokumentace k Azure Sphere (starší verze). Azure Sphere (starší verze) se vyřazuje 27. září 2027 a uživatelé musí do této doby migrovat do Azure Sphere (integrované). K zobrazení dokumentace k Azure Sphere (integrované) použijte selektor verzí umístěný nad obsahem.

Doporučujeme, aby kód vaší aplikace splňoval minimální standard kvality definovaný v tomto tématu. Prostřednictvím našich partnerství se zákazníky, kteří se snaží zlepšit své aplikace nasazené v produkčním prostředí, jsme zjistili některé běžné problémy, které při opravení zlepšují výkon aplikací.

Běžné problémy

  • Při nastavování sady cílových rozhraní API doporučujeme používat nejnovější nástroje CMake a Azure Sphere a nakonec kompilovat konečné binární soubory vydané verze nastavením AZURE_SPHERE_TARGET_API_SET="latest-lts". Další podrobnosti najdete v tématu Kódování pro obnovitelné zabezpečení.

Poznámka:

Když konkrétně vytváříte balíčky imagí, které se mají načíst bokem v rámci výrobního procesu, nastavte AZURE_SPHERE_TARGET_API_SET na odpovídající verzi operačního systému Azure Sphere, ve které bylo zařízení zdrojové nebo obnovené. Pokud to neuděláte, způsobí to, že operační systém Azure Sphere odmítne balíček image.

  • Až budete připraveni nasadit aplikaci do produkčního prostředí, nezapomeňte v režimu vydání zkompilovat konečné balíčky imagí.
  • Aplikace nasazené do produkčního prostředí se běžně zobrazují i přes upozornění kompilátoru. Vynucování zásad s nulovými upozorněními pro úplné sestavení zajišťuje, že se každé upozornění kompilátoru záměrně řeší. Toto jsou nejčastější typy upozornění, které důrazně doporučujeme vyřešit:
    • Implicitní upozornění související s převodem: Chyby se často zavádějí z důvodu implicitních převodů vyplývajících z počátečních rychlých implementací, které zůstaly nerevidované. Například kód, který má mnoho implicitních číselných převodů mezi různými typy čísel, může vést ke ztrátě kritické přesnosti nebo dokonce k chybám výpočtu nebo větvení. Pro správnou úpravu všech číselných typů se doporučuje jak úmyslná analýza, tak přetypování, nikoli pouze přetypování.
    • Vyhněte se změnám očekávaných typů parametrů: Při volání rozhraní API, pokud nejsou explicitně přetypována, mohou implicitní převody způsobit problémy, například přerušování vyrovnávací paměti při použití podepsaného číselného typu místo nepodepsaného číselného typu.
    • const-discarding warnings: Pokud funkce vyžaduje jako parametr typ const, může jeho přepsání vést k chybám a nepředvídatelným chováním. Důvodem upozornění je zajistit, aby parametr const zůstal nedotčený a při návrhu určitého rozhraní API nebo funkce zohlednil omezení.
    • Nekompatibilní upozornění ukazatele nebo parametrů: Ignorování tohoto upozornění může často skrýt chyby, které budou později obtížné sledovat. Odstranění těchto upozornění může pomoct zkrátit dobu diagnostiky jiných problémů s aplikací.
  • Nastavení konzistentního kanálu CI/CD je klíčem pro trvalou dlouhodobou správu aplikací, protože umožňuje snadné opětovné sestavování binárních souborů a jejich odpovídajících symbolů pro ladění starších verzí aplikací. Správná strategie větvení je také nezbytná pro sledování verzí a zabraňuje nákladnému místa na disku při ukládání binárních dat.
  • Pokud je to možné, definujte všechny běžné pevné řetězce global const char* jako místo jejich pevného kódování (například v printf příkazech), aby je bylo možné použít jako datové ukazatele v celém základu kódu a zároveň udržovat kód lépe udržovatelný. Při získávání společného textu z protokolů nebo manipulací s řetězci (například OKSucceededz názvů vlastností , , nebo JSON) a jeho globalizace na konstanty často vznikly úspory v části paměti dat jen pro čtení (označované také jako .rodata), což znamená úspory v paměti flash, které by se daly použít v jiných oddílech (například .text pro více kódu). Tento scénář se často přehlíží, ale může přinést významné úspory v paměti flash.

Poznámka:

Výše uvedené možnosti lze dosáhnout také jednoduše aktivací optimalizací kompilátoru (například -fmerge-constants v gcc). Pokud zvolíte tento přístup, zkontrolujte také výstup kompilátoru a ověřte, že byly použity požadované optimalizace, protože se mohou lišit v různých verzích kompilátoru.

  • V případě globálních datových struktur zvažte, pokud je to možné, možnost dát pevné délky přiměřeně malým členům pole místo použití ukazatelů na dynamicky přidělenou paměť. Příklad:
    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;
  • Vyhněte se přidělování dynamické paměti, kdykoli je to možné, zejména v rámci často označovaných funkcí.
  • V jazyce C vyhledejte funkce, které vracejí ukazatel na vyrovnávací paměť, a zvažte jejich převod na funkce, které vracejí odkazovaný ukazatel vyrovnávací paměti a jeho související velikost volajícím. Důvodem k tomu je, že vrácení pouze ukazatele na vyrovnávací paměť často vedlo k problémům s volajícím kódem, protože velikost vrácené vyrovnávací paměti není vynuceně potvrzena, a proto by mohla ohrozit konzistenci haldy. Příklad:
    // 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..])

Dynamické kontejnery a vyrovnávací paměti

Kontejnery, jako jsou seznamy a vektory, se také často používají ve vložených aplikacích jazyka C s upozorněním, že kvůli omezením paměti při používání standardních knihoven je obvykle potřeba je explicitně zakódovat nebo propojit jako knihovny. Tyto implementace knihoven můžou aktivovat náročné využití paměti, pokud nejsou pečlivě navržené.

Kromě typických staticky přidělených polí nebo vysoce dynamických implementací paměti doporučujeme přístup přírůstkového přidělování. Začněte například s prázdnou implementací fronty předem přidělených objektů N; ve frontě (N+1)nasdílení změn fronty roste o pevné X další předem přidělené objekty (N=N+X), které zůstanou dynamicky přidělené, dokud další přidání do fronty nepřeteče jeho aktuální kapacitu a zvýší přidělení paměti o další předem přidělené objekty X. Nakonec můžete implementovat novou komprimující funkci, která bude volat střídmě (protože by bylo příliš nákladné volat pravidelně), aby se uvolnit nevyužitá paměť.

Vyhrazený index dynamicky zachová počet aktivních objektů pro frontu, což může být omezeno na maximální hodnotu pro další ochranu před přetečením.

Tento přístup eliminuje "chatter" vygenerovaný průběžným přidělováním paměti a uvolněním v tradičních implementacích front. Podrobnosti najdete v tématu Správa a využití paměti. Podobné přístupy můžete implementovat pro struktury, jako jsou seznamy, pole atd.