Grunderna för kodning
Viktigt!
Det här är dokumentationen om Azure Sphere (Legacy). Azure Sphere (Legacy) upphör den 27 september 2027 och användarna måste migrera till Azure Sphere (integrerad) vid den här tiden. Använd versionsväljaren ovanför TOC för att visa dokumentationen om Azure Sphere (integrerad).
Vi föreslår att programkoden uppfyller en lägsta kvalitetsstandard enligt definitionen i det här avsnittet. Genom våra samarbeten med kunder som försöker förbättra sina produktionsdistribuerade program har vi hittat några vanliga problem, som när de är fasta förbättrar prestandan för program.
Vanliga problem
- När du anger mål-API:et rekommenderar vi att du använder de senaste CMake- och Azure Sphere-verktygen och slutligen kompilerar de slutliga versionsbinärfilerna genom att ange
AZURE_SPHERE_TARGET_API_SET="latest-lts"
. Mer information finns i Kodning för förnybar säkerhet.
Kommentar
När du specifikt skapar avbildningspaket som ska läsas in separat i en tillverkningsprocess anger du AZURE_SPHERE_TARGET_API_SET
till lämplig Version av Azure Sphere-operativsystemet som enheten har hämtats eller återställts till. Om du inte gör det kommer Azure Sphere OS att avvisa avbildningspaketet.
- När du är redo att distribuera ett program till produktion ska du kompilera de slutliga avbildningspaketen i versionsläge.
- Det är vanligt att se program som distribueras till produktion trots kompilatorvarningar. Att framtvinga en nollvarningsprincip för fullständiga versioner säkerställer att varje kompilatorvarning avsiktligt åtgärdas. Följande är de vanligaste varningstyperna, som vi starkt rekommenderar att du tar itu med:
- Implicita konverteringsrelaterade varningar: Buggar introduceras ofta på grund av implicita konverteringar till följd av inledande, snabba implementeringar som förblev ovissa. Kod som till exempel har många implicita numeriska konverteringar mellan olika numeriska typer kan resultera i kritiska precisionsförluster eller till och med beräknings- eller förgreningsfel. För korrekt justering av alla numeriska typer rekommenderas både avsiktlig analys och gjutning, inte bara gjutning.
- Undvik att ändra de förväntade parametertyperna: När du anropar API:er, om de inte uttryckligen konverteras, kan implicita konverteringar orsaka problem, till exempel överskrida en buffert när en signerad numerisk typ används i stället för en osignerad numerisk typ.
- const-discarding-varningar: När en funktion kräver en const-typ som en parameter kan det leda till buggar och oförutsägbart beteende. Anledningen till varningen är att säkerställa att const-parametern förblir intakt och tar hänsyn till begränsningarna när du utformar ett visst API eller en viss funktion.
- Inkompatibla pekare- eller parametervarningar: Om du ignorerar den här varningen kan du ofta dölja buggar som blir svåra att spåra senare. Om du eliminerar dessa varningar kan du minska diagnostiden för andra programproblem.
- Att konfigurera en konsekvent CI/CD-pipeline är nyckeln till hållbar långsiktig programhantering, eftersom det enkelt tillåter att binärfiler och deras korrespondentsymboler återskapas för felsökning av äldre programversioner. En lämplig förgreningsstrategi är också viktig för att spåra versioner och undviker kostsamt diskutrymme vid lagring av binära data.
Minnesrelaterade problem
- När det är möjligt definierar du alla vanliga fasta strängar som
global const char*
i stället för att hårdkoda dem (till exempel inomprintf
kommandon) så att de kan användas som datapekare i hela kodbasen samtidigt som koden hålls mer underhållbar. I verkliga program har insamling av vanlig text från loggar eller strängmanipuleringar (till exempelOK
,Succeeded
, eller JSON-egenskapsnamn) och globalisering av den till konstanter ofta resulterat i besparingar i avsnittet skrivskyddat dataminne (även kallat .rodata), vilket innebär besparingar i flashminne som kan användas i andra avsnitt (till exempel .text för mer kod). Det här scenariot förbises ofta, men det kan ge betydande besparingar i flashminnet.
Kommentar
Ovanstående kan också uppnås helt enkelt genom att aktivera kompilatoroptimeringar (till exempel -fmerge-constants
på gcc). Om du väljer den här metoden kontrollerar du även kompilatorns utdata och kontrollerar att önskade optimeringar har tillämpats, eftersom dessa kan variera mellan olika kompilatorversioner.
- För globala datastrukturer bör du när det är möjligt överväga att ge fasta längder till ganska små matrismedlemmar i stället för att använda pekare för dynamiskt allokerat minne. Till exempel:
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;
- Undvik dynamisk minnesallokering när det är möjligt, särskilt inom ofta kallade funktioner.
- I C letar du efter funktioner som returnerar en pekare till en minnesbuffert och överväger att konvertera dem till funktioner som returnerar en refererad buffertpekare och dess relaterade storlek till anroparna. Anledningen till att göra detta är att returnera bara en pekare till en buffert har ofta lett till problem med anropskoden, eftersom storleken på den returnerade bufferten inte med två skäl erkänns och därför kan äventyra heapens konsekvens. Till exempel:
// 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..])
Dynamiska containrar och buffertar
Containrar som listor och vektorer används också ofta i inbäddade C-program, med förbehållet att de på grund av minnesbegränsningar vid användning av standardbibliotek vanligtvis måste kodas eller länkas uttryckligen som bibliotek. Dessa biblioteksimplementeringar kan utlösa intensiv minnesanvändning om de inte är noggrant utformade.
Förutom typiska statiskt allokerade matriser eller mycket minnesdynamiska implementeringar rekommenderar vi en inkrementell allokeringsmetod. Börja till exempel med en tom köimplementering av förallokerade N-objekt. på den (N+1):e kö push-överföringen växer kön med ett fast X ytterligare förallokerade objekt (N=N+X), som förblir dynamiskt allokerade tills ett annat tillägg till kön kommer att spilla över sin aktuella kapacitet och öka minnesallokeringen med X ytterligare förallokerade objekt. Du kan så småningom implementera en ny komprimeringsfunktion för att anropa sparsamt (eftersom det skulle vara för dyrt att anropa regelbundet) för att frigöra oanvänt minne.
Ett dedikerat index bevarar dynamiskt antalet aktiva objekt för kön, vilket kan begränsas till ett maximalt värde för ytterligare spillskydd.
Den här metoden eliminerar "chatter" som genereras av kontinuerlig minnesallokering och frigöring i traditionella köimplementeringar. Mer information finns i Minneshantering och minnesanvändning. Du kan implementera liknande metoder för strukturer som listor, matriser och så vidare.