Basisprincipes van coderen
We raden u aan dat uw toepassingscode voldoet aan een minimale kwaliteitsnorm zoals gedefinieerd in dit onderwerp. Door onze partnerschappen met klanten die hun in de productie geïmplementeerde toepassingen willen verbeteren, hebben we enkele veelvoorkomende problemen gevonden, die bij het oplossen de prestaties van toepassingen verbeteren.
Veelvoorkomende problemen
- Bij het instellen van de doel-API-set raden we u aan de nieuwste CMake- en Azure Sphere-hulpprogramma's te gebruiken en uiteindelijk de definitieve binaire releasebestanden samen te stellen door in te stellen
AZURE_SPHERE_TARGET_API_SET="latest-lts"
. Zie Codering voor hernieuwbare beveiliging voor meer informatie.
Opmerking
Wanneer u specifiek installatiekopieën maakt die moeten worden sideloaden binnen een productieproces, stelt AZURE_SPHERE_TARGET_API_SET
u in op de juiste Versie van het Azure Sphere-besturingssysteem waarnaar het apparaat is opgehaald of hersteld. Als u dit niet doet, wordt het installatiekopieënpakket door het Azure Sphere-besturingssysteem geweigerd.
- Wanneer u klaar bent om een toepassing in productie te implementeren, moet u ervoor zorgen dat u de uiteindelijke installatiekopieënpakketten compileert in de releasemodus.
- Het is gebruikelijk om toepassingen te zien die in productie zijn geïmplementeerd, ondanks compilerwaarschuwingen. Het afdwingen van een zero-warnings-beleid voor volledige builds zorgt ervoor dat elke compilerwaarschuwing opzettelijk wordt aangepakt. Hier volgen de meest voorkomende waarschuwingstypen, die we u ten zeerste aanbevelen:
- Impliciete waarschuwingen voor conversie: Fouten worden vaak geïntroduceerd vanwege impliciete conversies die het gevolg zijn van initiële, snelle implementaties die niet zijn bekeken. Code met veel impliciete numerieke conversies tussen verschillende numerieke typen kan bijvoorbeeld leiden tot kritieke precisieverlies of zelfs berekenings- of vertakkingsfouten. Voor het correct aanpassen van alle numerieke typen worden zowel opzettelijke analyse als cast aanbevolen, niet alleen casten.
- Vermijd het wijzigen van de verwachte parametertypen: Bij het aanroepen van API's kunnen impliciete conversies problemen veroorzaken als ze niet expliciet worden gecast. Bijvoorbeeld het overlopen van een buffer wanneer een ondertekend numeriek type wordt gebruikt in plaats van een niet-ondertekend numeriek type.
- waarschuwingen negeren: Wanneer een functie een const-type als parameter vereist, kan het negeren ervan leiden tot fouten en onvoorspelbaar gedrag. De reden voor de waarschuwing is om ervoor te zorgen dat de parameter const intact blijft en rekening houdt met de beperkingen bij het ontwerpen van een bepaalde API of functie.
- Incompatibele aanwijzer- of parameterwaarschuwingen: Het negeren van deze waarschuwing kan vaak fouten verbergen die later moeilijk te traceren zijn. Het elimineren van deze waarschuwingen kan helpen de diagnosetijd van andere toepassingsproblemen te verkorten.
- Het instellen van een consistente CI/CD-pijplijn is essentieel voor duurzaam toepassingsbeheer op lange termijn, omdat het eenvoudig is om binaire bestanden en de bijbehorende correspondentsymbolen opnieuw te bouwen voor het opsporen van fouten in oudere toepassingsreleases. Een juiste vertakkingsstrategie is ook essentieel voor het bijhouden van releases en voorkomt kostbare schijfruimte bij het opslaan van binaire gegevens.
Geheugengerelateerde problemen
- Definieer indien mogelijk alle algemene vaste tekenreeksen als
global const char*
in plaats van ze hard te coderen (bijvoorbeeld binnenprintf
opdrachten), zodat ze kunnen worden gebruikt als gegevenspointers in de hele codebasis, terwijl de code beter onderhouden blijft. In echte toepassingen heeft het verzamelen van algemene tekst uit logboeken of tekenreeksmanipulaties (zoalsOK
,Succeeded
of JSON-eigenschapsnamen) en het globaliseren ervan naar constanten vaak geleid tot besparingen in de sectie alleen-lezen gegevensgeheugen (ook wel bekend als .rodata), wat resulteert in besparingen in flashgeheugen die in andere secties kunnen worden gebruikt (zoals .text voor meer code). Dit scenario wordt vaak over het hoofd gezien, maar het kan aanzienlijke besparingen in flashgeheugen opleveren.
Opmerking
Het bovenstaande kan ook worden bereikt door compileroptimalisaties te activeren (zoals -fmerge-constants
op gcc). Als u deze methode kiest, controleert u ook de uitvoer van de compiler en controleert u of de gewenste optimalisaties zijn toegepast, omdat deze kunnen variëren tussen verschillende compilerversies.
- Voor globale gegevensstructuren kunt u, indien mogelijk, overwegen om een vaste lengte toe te wijzen aan redelijk kleine matrixleden in plaats van aanwijzers te gebruiken voor dynamisch toegewezen geheugen. Bijvoorbeeld:
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;
- Vermijd waar mogelijk dynamische geheugentoewijzing, met name binnen veelgebruikte functies.
- Zoek in C naar functies die een aanwijzer retourneren naar een geheugenbuffer en overweeg deze te converteren naar functies die een bufferpointer waarnaar wordt verwezen en de bijbehorende grootte aan de bellers retourneren. De reden hiervoor is dat het retourneren van alleen een aanwijzer naar een buffer vaak heeft geleid tot problemen met de aanroepende code, omdat de grootte van de geretourneerde buffer niet geforceerd wordt bevestigd en daarom de consistentie van de heap in gevaar kan brengen. Bijvoorbeeld:
// 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 containers en buffers
Containers zoals lijsten en vectoren worden ook vaak gebruikt in ingesloten C-toepassingen, met het voorbehoud dat ze vanwege geheugenbeperkingen bij het gebruik van standaardbibliotheken meestal expliciet moeten worden gecodeerd of gekoppeld als bibliotheken. Deze bibliotheek-implementaties kunnen intensief geheugengebruik activeren als ze niet zorgvuldig zijn ontworpen.
Naast de gebruikelijke statisch toegewezen matrices of zeer geheugendynamische implementaties, raden we een methode voor incrementele toewijzing aan. Begin bijvoorbeeld met een lege wachtrij-implementatie van vooraf toegewezen N-objecten; op de push (N+1)th queue groeit de wachtrij met een vaste X extra vooraf toegewezen objecten (N=N+X), die dynamisch toegewezen blijven totdat een andere toevoeging aan de wachtrij de huidige capaciteit overloopt en de geheugentoewijzing met X extra vooraf toegewezen objecten toeneemt. U kunt uiteindelijk een nieuwe verdichtingsfunctie implementeren om spaarzaam aan te roepen (omdat het te duur zou zijn om regelmatig aan te roepen) om ongebruikt geheugen vrij te maken.
Een toegewezen index behoudt dynamisch het aantal actieve objecten voor de wachtrij, dat kan worden beperkt tot een maximumwaarde voor extra overloopbeveiliging.
Deze benadering elimineert de 'chatter' die wordt gegenereerd door continue geheugentoewijzing en deallocatie in traditionele wachtrij-implementaties. Zie Geheugenbeheer en gebruik voor meer informatie. U kunt vergelijkbare benaderingen implementeren voor structuren zoals lijsten, matrices, enzovoort.