Static w embedded
February 29, 2024

Czasami na spotkaniu może się zdarzyć, że pojawia się pytanie, które z pozoru wydaje się łatwe, jednakże rodzi szereg dalszych zapytań. Tak też miało miejsce w omawianej sytuacji, kiedy to z pozornie prostej kwestii dotyczącej słowa kluczowego w języku C, "Static", wyłoniła się cała seria pytań.

A oto nasza lista:

  1. Gdzie przechowywana jest niezainicjalizowana zmienna statyczna?
  2. Gdzie przechowywana jest zainicjalizowana zmienna statyczna?
  3. Kiedy zmienna statyczna jest inicjalizowana i jaką wartością?
  4. Gdzie przechowywana jest stała statyczna?
  5. Kiedy stała statyczna jest inicjalizowana i jaką wartością?
  6. Gdzie przechowywana jest zmienna statyczna funkcji?
  7. Kiedy zmienna statyczna funkcji jest inicjalizowana i jaką wartością?
  8. Gdzie przechowywana jest stała statyczna funkcji?
  9. Kiedy stała statyczna funkcji jest inicjalizowana i jaką wartością?
  10. Czy zmienną statyczną funkcji można zainicjalizować argumentem funkcji?
    BONUS -> Co z tablicą znaków i wskaźnikiem na tablicę znaków?

Środowisko

Do zaprezentowania wyników posłużyłem się poniższym środowiskiem. Sama platforma uruchomieniowa nie powinna mieć to większego znaczenia dla zrozumienia specyfiki wykorzystania danych statycznych w środowisku embedded.

  • Target: STM32WB55
  • IDE: STM32CubeIDE
  • Toolchain (kompilator, linker itp) : arm-none-eabi w wersji 10.3-2021.10 dostarczony razem z IDE

Kod był kompilowany z flagami:

  • ffunction-section  - przydziela każdej funkcji osobną sekcję w pamięci
  • fdata-section - przydziela wszystkim danym osobne sekcje w pamieci
  • g3  - włączenie najwyższego poziomu debugowania
  • O0 - wyłączenie optymalizacji

Powyższe flagi pozwolą nam łatwiej zlokalizować nasze dane statyczne w pliku z mapą pamieci oraz ułatwią poruszanie się po kodzie źródłowym i analizie podczas debugowania.

Wśród flag linkera zastosowałem:

  • Wl,-Map=path_to_map_file flaga ta włącza generację plików z mapą pamieci niezbędną nam do odszukania poszczególnych danych;

Kod bazowy

Pierwotna postać pliku main.c ma postać jak poniżej i będzie wykorzystana w każdym przykładzie jako punkt startowy.

#include "platform.h" int main(void) { platform_init(); while (1) {} }

Jak widać poza inicjalizacją platformy przy użyciu funkcji platform_init() mamy wyłącznie pętle nieskończoną. Funkcja platform_init() odpowiada za inicjalizację naszej platformy sprzętowej czyli mikrokontrolera stm32wb55.

Dla tak przygotowanego kodu zapisałem w osobnym katalogu plik mapy pamięci (tzw. *.map file) oraz rozmiary poszczególnych sekcji pamięci.  Przydadzą się one w dalszym etapie jako punkt odniesienia aby przedstawiać różnice jakie wprowadza użycie danych statycznych.

section size addr .isr_vector 0x13c 0x8000000 .text 0x1e58 0x800013c .rodata 0x178 0x8001f94 .ARM 0x8 0x800210c .init_array 0x4 0x8002114 .fini_array 0x4 0x8002118 .data 0x70 0x20000004 .bss 0x24 0x20000074 ._user_heap_stack 0x600 0x20000098 .ARM.attributes 0x30 0x0 .debug_info 0x411c 0x0 .debug_abbrev 0xd52 0x0 .debug_aranges 0x4d8 0x0 .debug_ranges 0x430 0x0 .debug_macro 0x1965c 0x0 .debug_line 0x57a0 0x0 .debug_str 0x8dd43 0x0 .comment 0x50 0x0 .debug_frame 0x1200 0x0

Już teraz patrząc na adresy i znając nasz mikrokontroler możemy powiedzieć, w jakiej pamięci znajdują się poszczególne sekcje:

Realizacja

1 . Gdzie przechowywana jest niezainicjalizowana zmienna statyczna?

W pierwszej kolejności dodałem niezainicjalizowaną zmienną statyczną do naszego pliku main.c i porównałem rozmiary sekcji oraz pliki *.map (po lewej postać bazowa, po prawej wynik dla naszego nowego kodu).

Porównanie rozmiarów poszczególnych sekcji:

Porównanie rozmiarów sekcji

Porównanie plików *.map:

Porównanie plików *.map

Z porównania rozmiarów sekcji widzimy, że dodanie jednej zmiennej statycznej w pliku main.c spowodowało zwiększenie się sekcji .bss o 4 bajty oraz sekcji ._user_heap_stack o 4 bajty.

Dzięki plikom  *.map możemy stwierdzić, że nasza nowa niezainicjalizowana zmienna wylądowała w sekcji .bss. a zwiększenie sekcji ._user_heap_stack o 4 bajty spowodowane jest wyrównaniem danych do 8 bajtów (tzw. alignment). Gdybym zdefiniował dwie zmienne statyczne to obie wylądowałyby w sekcji .bss a sekcja ._user_heap_stack powróciłaby do swojego oryginalnego rozmiaru.

Czym jest sekcja .bss i sekcja ._user_heap_stack?

Obie te sekcje znajdują się w pamięci RAM tak wiec aby dobrze odpowiedzieć na to pytanie możemy zajrzeć do skryptu linkera (poniżej fragment przedstawiający skrypt linkera).Poniższy zapis oznacza nie mniej nie więcej jak to, że dla danych w tych sekcjach zarezerwowana została przestrzeń w pamięci RAM1.

/* Uninitialized data section */ . = ALIGN(4); .bss : { /* This is used by the startup in order to initialize the .bss section */ _sbss = .; /* define a global symbol at bss start */ __bss_start__ = _sbss; *(.bss) *(.bss*) *(COMMON) . = ALIGN(4); _ebss = .; /* define a global symbol at bss end */ __bss_end__ = _ebss; } >RAM1 /* User_heap_stack section, used to check that there is enough RAM left */ ._user_heap_stack : { . = ALIGN(8); PROVIDE ( end = . ); PROVIDE ( _end = . ); . = . + _Min_Heap_Size; . = . + _Min_Stack_Size; . = ALIGN(8); } >RAM1

Zwróćmy uwagę na linijkę 1 i 17

  • .bss - to sekcja przechowująca niezainicjalizowane zmienne (zarówno globalne jak i statyczne)
  • ._user_heap_stack to sekcja stworzona na potrzeby pilnowania wystarczającej ilość pamięci RAM na potrzeby stosu (stack) oraz sterty (heap).

2 . Gdzie przechowywana jest zainicjalizowana zmienna statyczna?

Do tego punktu podchodzimy jak do poprzedniego. Dodajemy wyłącznie wartość inicjalizacyjną do zmiennej statycznej i obserwujemy co się zmieni w rozmiarach sekcji oraz pliku *.map.

#include "platform.h" static int inintializedStaticVariable = 42; int main(void) { platform_init(); while (1) {} }

Porównanie rozmiarów poszczególnych sekcji:

Porównanie rozmiarów sekcji

Porównanie plików *.map:

Porównanie *.map

Z powyższego wynika, że nasza nowa zainicjalizowana zmienna wylądowała w sekcji pamięci .data zajmując tam 4 bajty. Tak jak poprzednio z racji wyrównania pamięci do 8 bajtów w sekcji ._user_heap_stack pojawił się dodatkowy obszar 4 bajtów.

Czym jest sekcja .data?

Sekcję  ._user_heap_stack opisałem już wcześniej więc skupię się na sekcji .data.

Ponownie zaglądam do skryptu linkera i znajduję interesujący mnie fragment. Jak widać w opisie do tej sekcji lądują zmienne posiadające wartość inicjalizacyjną.  

/* Initialized data sections goes into RAM, load LMA copy after code */ .data : { . = ALIGN(4); _sdata = .; /* create a global symbol at data start */ *(.data) /* .data sections */ *(.data*) /* .data* sections */ *(.RamFunc) /* .RamFunc sections */ *(.RamFunc*) /* .RamFunc* sections */ . = ALIGN(4); _edata = .; /* define a global symbol at data end */ } >RAM1 AT> FLASH

3 . Gdzie przechowywana jest niezainicjalizowana zmienna statyczna?

W pierwszej kolejności kod:

#include "platform.h" static int uninintializedStaticVariable; static int inintializedStaticVariable = 42; int main(void) { platform_init(); while (1) {} }

We wcześniejszych przykładach, gdy prezentowałem fragmenty skryptu linkera przy klamrach zamykających daną sekcję można znaleźć oznaczenia >RAM1 lub >RAM1 AT> FLASH

  • >RAM1 - w pamięci RAM zarezerwowano przestrzeń dla zmiennych z danej sekcji.
  • >RAM1 AT> FLASH -  w pamięci RAM zarezerwowano przestrzeń dla zmiennych z danej sekcji. Ponadto  ma ona swój odpowiednik w pamięci FLASH

I co nam to daje?

Podczas kompilacji i linkowania w pamieci RAM zarezerwowana będzie przestrzeń dla sekcji .bss (ze zmiennymi niezainicjalizowanymi) jak i dla sekcji .data (zmienne zainicjalizowane). Dodatkowo w pamięci Flash (pamięć programu) umieszczone zostaną wartości użyte do inicjalizacji zmiennych z sekcji .data.

Podczas uruchamiania naszego oprogramowania zanim wywołana zostanie funkcja main() dzieje się całkiem sporo. Wszystko zaczyna sie od punktu startowego całego firmware zdefiniowanego w skrypcie linkera. Określa on miejsce od którego rozpocznie się wykonywanie naszej aplikacji.

/* Entry Point */ ENTRY(Reset_Handler)

Wiem już, że aplikacja rozpoczyna wykonywanie od funkcji Reset_Handler. Jej implementację zazwyczaj znajdziemy w pliku startup.

Reset_Handler: ldr r0, =_estack mov sp, r0 /* set stack pointer */ /* Call the clock system initialization function.*/ bl SystemInit /* Copy the data segment initializers from flash to SRAM */ INIT_DATA _sdata, _edata, _sidata /* Zero fill the bss segments. */ INIT_BSS _sbss, _ebss INIT_BSS _sMB_MEM2, _eMB_MEM2 /* Call static constructors */ bl __libc_init_array /* Call the application s entry point.*/ bl main

Poza ustawieniem wskaźnika stosu wykonywane są kolejno:

  • SystemInit - Inicjalizacja naszego mikrokontrolera (tylko najważniejsze z najważniejszych ustawień)
  • INIT_DATA - Inicjalizacja sekcji .data w pamieci RAM danymi z pamięci FLASH
  • INIT_BSS - Inicjalizacja sekcji .bss w pamieci RAM. Domyślnie jest to wypełnienie całej sekcji  wartościami 0.
  • __libc_init_array - Wywołanie statycznych konstruktorów
  • main - Skok do głównej funkcji naszego programu

Aby to lepiej zobrazować posłuże się małym rysunkiem i kilkoma krokami z sesji debugowej kodu.

Sekcje danych podczas uruchamiania programu

Przed inicjalizacją sekcji .data:

Po inicjalizacji sekcji .data, przed inicjalizacją sekcji .bss:

Po inicjalizacji sekcji .data oraz .bss:

Podsumowując to pytanie, zarówno statyczne zmienne zainicjalizowane jak i niezainicjalizowane są inicjalizowane zgodnie z plikiem startup. Dane do zainicjalizowanej zmiennej statycznej kopiowane są z pamięci Flash, a do niezainicjalizowanych zmiennych statycznych przypisywane są domyślnie zera (ale można to zmienić).  Przypisywanie wartości 0x00 do niezainicjalizowanych zmiennych statycznych jest zgodne ze standardem ISO 9899 sekcja 6.7.9.

4 . Gdzie przechowywana jest stała statyczna?

Czas iść dalej w głąb lasu i zajrzeć, co się stanie, jeśli zamiast zmiennymi zajmiemy się stałymi. Początkowa baza kodu jest identyczna jak wcześniej. Aby trochę skrócić całość zajmę się teraz stałymi zarówno tymi zainicjalizowanymi na początku jak i niezainicjalizowanymi. Pominę fakt, że osobiście nie widzę żadnego powodu aby ktokolwiek potrzebował niezainicjalizowaną stała. Kompilator pozwala nam na stworzenie takiej więc ją tutaj sprawdzimy.

#include "platform.h" static const int uninintializedStaticConstant; static const int inintializedStaticConstant = 100; int main(void) { platform_init(); while (1) {} }

Porównanie rozmiarów poszczególnych sekcji:

Porównanie plików *.map:

Jak widać na powyższym zmienił nam się rozmiar sekcji .rodata o 8 bajtów. Nie trudno się domyśleć, że to właśnie do tej sekcji trafiły obie nasze stałe. .rodata jest sekcją w pamięci Flash przechowująca stałe dane przeznaczone wyłącznie do odczytu (Read only data).

5 . Kiedy stała statyczna jest inicjalizowana i jaką wartością?

Wiemy już, że stałe przechowywane są w pamieci Flash w sekcji .rodata i są one wyłącznie do odczytu. Znając adresy poszczególnych stałych możemy odczytać ich wartości (np. w trakcie sesji debugowej wykorzystując widok Memory).

W naszym przykładzie:  

  • Pod adresem 0x08002804 znajduje sie nasza stała niezainicjalizowana (z wartością 0)
  • Pod adresem 0x08002808 znajduje się nasza stała zainicjalizowana (z wartością 0x64 czyli 100)
Stałe w sekcji .rodata

Stałe te są inicjalizowane w trakcie kompilacji na podstawie przypisanych wartości i nie ulegają zmienie w trakcie trwania programu (choć i na to są sposoby).  Stałe niezainicjalizowane posiadają wartość 0, stałe zainicjalizowane posiadają wartość jaka została do nich przypisana podczas definicji.

6 . Gdzie przechowywana jest zmienna statyczna funkcji?

Omówiliśmy już zmienne i stałe, które są zdefiniowane globalnie dla konkretnej jednostki translacyjnej, teraz zobaczmy, jak będą się zachowywały dane zdefiniowane lokalnie w obrębie pojedynczej funkcji.Na potrzeby tego fragmentu przygotowałem nowy kod bazowy. Tak prosty jak to tylko możliwe.

#include "platform.h" void foo(int const arg) { } int main(void) { platform_init(); foo(0x42); while (1) {} }

Dodałem jedną funkcję foo(int const) przyjmującą jeden argument i wywołałem ją z funkcji main() ze stałą wartością.

Do tak przygotowanego kodu bazowego dodałem niezainicjalizowaną zmienną statyczną w obrębie funkcji foo() i porównałem rozmiary sekcji oraz pliki *.map.

#include "platform.h" void foo(int const arg) { static int uninitializedStaticVar; } int main(void) { platform_init(); foo(0x42); while (1) {} }

Porównanie rozmiarów poszczególnych sekcji:

Porównanie plików *.map:

Szybko zauważamy, że zmienna ta wylądowała w sekcji .bss pamięci RAM. Przy okazji ponownie zwiększyła się sekcja ._user_heap_stack ze względu na wyrównanie do 8 bajtów. Dokładnie tak samo jak to się działo przy zmiennych zdefiniowanych w obrębie pliku.

Co jeśli zmienną statyczną funkcji zainicjalizujemy konkretną wartością?

#include "platform.h" void foo(int const arg) { static int initializedStaticVar = 0x99; } int main(void) { platform_init(); foo(0x42); while (1) {} }

Porównanie rozmiarów poszczególnych sekcji:

Porównanie plików *.map:

Tutaj identycznie jak przy statycznych zmiennych pliku, nasza lokalna statyczna zmienna funkcji wylądowała w sekcji .data.

7 . Kiedy zmienna statyczna funkcji jest inicjalizowana i jaką wartością?

Zmienne lokalne w funkcji, niezależnie od tego, czy są inicjalizowane wartością początkową, czy nie, trafiają do tych samych sekcji pamięci, co zmienne statyczne zdefiniowane globalnie w pliku.

#include "platform.h" void foo(int const arg) { static int uninitializedStaticVar; static int initializedStaticVar = 0x25; } int main(void) { platform_init(); foo(0x42); while (1) {} }

W związku z tym możemy stwierdzić, że inicjalizują się identycznie podczas startupu.

  • Zmienna z wartością początkową jest inicjalizowana daną z pamięci flash;
  • Zmienna bez wartości zostaje zapisana wartością zero;
Inicjalizacja w startup’ie

NOTE: Tutaj należy wspomnieć, że w języku C++ zmienne statyczne funkcji są inicjalizowane przy pierwszym wywołaniu funkcji.
Więcej można poczytać tutaj.

8. Gdzie przechowywana jest stała statyczna funkcji?

Kolejny krok to stała statyczna funkcji. Dla uproszczenia dodałem dwie stałe do naszej funkcji foo i przeanalizowałem rozmiary sekcji i pliki *.map.
Przypomnę, że nie nie widzę powodu aby ktokolwiek potrzebował stałą niezainicjalizowaną w swoim projekcie. Kompilator pozwala taką stworzyć, więc ją sprawdzamy.

#include "platform.h" void foo(int const arg) { static const int uninitializedStaticConstant; static const int initializedStaticConstant= 0x25; } int main(void) { platform_init(); foo(0x42); while (1) {} }

Porównanie rozmiarów poszczególnych sekcji:

Porównanie plików *.map:

Tutaj nasze stałe również zachowują się identycznie jak w przypadku stałych statycznych zdefiniowanych globalnie dla pliku i lądują w pamieci Flash w sektorze .rodata (sekcja wyłącznie do odczytu).

9. Kiedy stała statyczna funkcji jest inicializowana i jaką wartością?

Stałe nie są inicjalizowane. Wartości są im przypisane w momencie kompilacji i nie zmieniają się w czasie pracy programu. Sprawdzając w pamięci adresy naszych stałych możemy się upewnić jakie przechowują wartości.

  • Pod adresem 0x0800281c  umieszczona jest nasza zainicjalizowana stała i przechowuje wartość 0x25
  • Pod adresem 0x08002820 umieszczona jest stała niezainicjalizowana i przechowuje wartość 0x00
Stałe w sekcji .rodata

10. Czy zmienną statyczną funkcji można zainicjalizować argumentem funkcji?

Na koniec zostawiłem sobie pytanie co do którego sam nie miałem pewności jak powinienem odpowiedzieć. Skoro zmienna statyczna funkcji może mieć wartość 0 (jeśli jest niezainicjalizowana) bądź wartość z góry ustaloną (kopiowaną z pamięci Flash) to czy możliwe jest zainicjalizowanie takiej zmiennej wartością przekazaną do funkcji poprzez argument?
Opis brzmi strasznie ale w kodzie nie wygląda to tak źle.

#include "platform.h" void foo(int const arg) { static int initializedStaticVariable = arg; } int main(void) { platform_init(); foo(0x42); while (1) {} }

Otóż w tym przypadku kompilator zgłosi błąd domagając się stałej (znanej podczas kompilacji) wartości inicjalizującej dla zmiennej statycznej.

../Core/Src/main.c: In function 'foo': ../Core/Src/main.c:5:42: error: initializer element is not constant 5 | static int initializedStaticVariable = arg; make[1]: *** [Core/Src/subdir.mk:34: Core/Src/main.o] Error 1 make: *** [makefile:61: all] Error 2

Jest to również zgodne ze standardem ISO 9899  Sekcja 6.7.9 Initialization.

"All the expressions in an initializer for an object that has static or thread storage duration shall be constant expressions or string literals."

Tak więc wszystkie wartości używane do inicjalizacji muszą być stałym wyrażeniem, czyli wartością znaną w czasie kompilacji.

BONUS -> Co z tablicą znaków i wskaźnikiem na tablicę znaków?

A co z wskaźnikiem, który wskazuje na ciąg znaków "string" przechowywany w stałej części pamięci?
A co z tablicą, która zawiera ciąg znaków "string"?

char* string = "string" char string[] = "string"

Również polecam wrzucić sobie taki lub podobny kawałek kodu i podejrzeć zawartość plików *.map przeanalizować co się gdzie znajduje oraz jak się zachowuje po uruchomieniu (wystarczy sam startup).

#include "stddef.h" char *glob_string_ptr = "glob_string_ptr"; char glob_string_arr[] = "glob_string_arr"; int main(void) { size_t s_gsp = sizeof(glob_string_ptr); size_t s_gsa = sizeof(glob_string_arr); glob_string_ptr[0] = 'z'; glob_string_arr[0] = 'r'; platform_init(); while (1) {} }

W mapie pamięci znajdziemy:

Mapa pamięci
  • 0x20000004 → __dso_handle  →  obiekt używany przy wykonywaniu globalnych destruktorów (o nim innym razem)
  • 0x20000008 → glob_string_ptr  →  czyli nasz wskaźnik na napis (rozmiar wskaźnika 4 bajty)
  • 0x2000000c → glob_string_arr  →  czyli tablicę 16 znaków (rozmiar 16 bajtów = 15 znaków i terminator ‘\0’)

Pamiętamy że:

  • sekcja .data, w której znajdują się nasze obiekty to tylko zarezerwowany obszar pamięci RAM dla zainicjalizowanych danych/zmiennych
  • sekcja .data ma swój odpowiednik z danymi inicjalizujacymi w pamięci Flash (u nas zaczyna się pod adresem wskazywanym przez _sidata czyli 0x08002924
  • dane inicjalizacyjne są kopiowane z pamieci Flash do sekcji .data podczas wykonywania startupu

Skoro wiemy, że dane spod adresu 0x08002924 są kopiowane do pamięci RAM do sekcji .data pod adres 0x20000004. Podejrzyjmy co się tam znajduje:

Adresy po lewej podawane są jako offset do adresu 0x80000000.  
Nie bez powodu zaznaczyłem taki obszar pamieci flash. Przeanalizujmy krok po kroku co tutaj mamy:

  • Spod adresu 0x08002924 kopiujemy dane 0000 0000 b427 0008 676c 6f62 5f73 7472 696e 675f 6172 7200 …… pod adres 0x20000004
  • W ten oto sposób nasze kolejne zmienne dostają wartości (pamietając o little endian):
    0x20000004 → __dso_handle  → 0x00000000
    0x20000008 → glob_string_ptr  →  0x080027b4 → 676c 6f62 5f73 7472 696e 675f 7074 7200
    0x2000000c → glob_string_arr  →  676c 6f62 5f73 7472 696e 675f 6172 7200

Jak widać:

  • __dso_handle jest wyzerowany = NULL
  • wskaźnik glob_string_ptr dostał adres do napisu “glob_string_ptr” znajdującego się pamięci Flash.
  • tablica glob_string_arr została zainicjalizowana napisem “glob_string_arr”. Napis ten znajduje się teraz w pamięci RAM

Dla pewności odszukajmy w mapie pamieci również wpisu z adresem 0x080027b4

Mapa pamięci - string

Widzimy tutaj, że nasz obszar pamięci .rodata przechowujący stałe dane zwiększył się o 16 bajtów i pochodzą one z pliku obiektowego main.o. Nie trudno zgadnąć, że wylądował tutaj napis, na który wskazuje wskaźnik glob_string_ptr co chwilę wcześniej udowodniliśmy przeglądając binarkę

W kodzie jaki przygotowaliśmy mamy także pobieranie rozmiarów obu tych obiektów.
Jak nie trudno zgadnąć (znając już zawartość pliku *.map):

  • Rozmiar glob_string_ptr wynosi 4 bajty (rozmiar wskaźnika)
  • Rozmiar glob_string_arr wynowsi 16 bajtów (tyle ile jest znaków + 1 dla termiantora ‘\0')

Dodatkowo:

  • napis zawarrty w obiekcie glob_string_arr możemy bez przeszkód zmieniać, gdyż cały obiekt i jego zawartość znajduje się w pamieci RAM
  • napisu zawartego w obiektu glob_string_ptr nie możemy zmodyfikować gdyż znajduje się pamieci Flash. Jedyne co możemy to zmodyfikować adres wskaźnika  

Kopiowanie danych inicjalizacyjnych z pamieci Flash do sekcji .data w pamięci RAM w startupie.

Przed:

Po:

Rozmiary obiektów oraz wpływ modyfikacji 1 znaku w obu obiektach. Jak widać zmiana nastąpiła w wyłącznie jednym obiekcie glob_string_arr

Podsumowanie

Z dziesiejszego artykułu wyciągnąć można kilka kluczowych informacji:

  • Niezainicjalizowane zmienne statyczne (globalne i lokalne) mają swoje miejsce w sekcji .bss pamieci RAM;
  • Zmienne znajdujące się w sekcji .bss  domyślnie inicjalizowane są podczas startupu wartością 0x00 ale można to zmienić odpowiednio modyfikując plik startup;
  • Zainicjaizowane zmienne statyczne (globalne i lokalne) mają swoje miejsce zarezerowane w sekcji .data w pamieci RAM, a wartości jakimi są inicjalizowane znajdują się w pamieci Flash;
  • Podczas startupu wartości inicjalizujące są kopiowane z pamieci Flash do sekcji .data w pamieci RAM;
  • Stałe statyczne (zainicjalizowane i niezainicjalizowane) (globalne i lokalne) mają swoje miejsce w sekcji .rodata (tylko do odczytu);
  • Niezainicjalializowane stałe statyczne oraz zmienne globalne zgodnie ze standardem są inicjalizowane wartością 0x00. Pomijamy fakt, że raczej nikomu nie jest potrzebna stała niezainicjalizowana;
  • Argumentem funkcji nie można zainicjalizować bezpośrednio zmiennej statycznej. Do inicjalizacji musi być wykorzystana wartość znana w czasie kompilacji;

O czym należy pamiętać po przeczytaniu tego artykułu:

  • Analizowałem tutaj kod wyłącznie w języku C. Jeśli chodzi o C++ to tam są pewne odstępstwa od przedstawionych wyników i wniosków
  • Mam całkowicie wyłączoną optymalizację. Przy włączonej optymalizacji (w zależności od poziomu) toolchain potrafi wprowadzić znaczne modyfikacje i uproszczenia;

REFERENCJE