Skocz do zawartości
ethanak

Różnica między #define, a const int

Pomocna odpowiedź

Poniższa dyskusja została wydzielona z innego tematu od poniższego miejsca:

-----

No - różnica jest, i to wielka.

const tworzy zmienną, która nie pozwala do siebie nic wstawić. Możliwy jest odnośnik lub wskaźnik do takiej zmiennej, a zmienna zajmuje ileśtam bajtów w RAM-ie.

#define nic nie tworzy, po prostu preprocesor w miejsce symbolu wstawia jego rozwinięcie. Nie ma żadnej zajętości pamięci, ale jeśli rozwinięciem jest literał - nie możemy użyć referencji czy wskaźnika.

Czyli:

void fun(const int *);

const int a = 7;
#define b 7
  
fun(&a); // dobrze
fun(&b); // błąd - preprocesor przetworzy to na fun(&7) 

Osobiście wolę #define czy enum, bo istnieją tylko w czasie kompilacji, a w maciupciej pamięci AVR-a (gdzie każdy bajt się liczy) nie widzę potrzeby zajmowania ani jednego bajtu na jakieś consty.

Przy okazji - nieprawidłowe użycie const może doprowadzić do bardzo trudnych do znalezienia błędów.

Oto przykład kodu w C/C++ - odpowiedzcie na pytania bez angażowania kompilatora :

#include <stdio.h>

const int a = 7;
void fun(int *x)
{
  *x=11;
}

int main(void)
{
   fun((int *)&a);
   printf("%d\n",a);
}

Czy ten program się skompiluje? Jeśli tak, jaki będzie wynik jego wykonania np. pod Linuksem?

Podobny szkic dla Arduino:

const int a = 7;

void setup(void)
{
  Serial.begin(9600); 
  *(int *)&a = 11;
  Serial.println(a);
   
}

void loop(void)
{
}

 

Czy program się skompiluje? Jeśli tak, co wypisze

  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites

W takim razie doprecyzuje, że w przypadku wykorzystywania const do deklaracji pinu (a tutaj mowa była o takim przypadku) nie będzie to robiło różnicy od strony praktycznej. Obie wersje zadziałają.

  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites
12 minut temu, Elvis napisał:

W poprawnie napisanym programie nic złego się nie wydarzy, nie będzie też marnowania zasobów.

W poprawnie napisanym tak...

  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites
58 minut temu, Elvis napisał:

Co więcej kod wynikowy obu wersji będzie taki sam.

Co możemy szybko i łatwo sprawdzić porównując wynikowy assembler obu wersji: https://godbolt.org/z/AzPxGk który dla obu wersji jest identyczny pod AVR gcc 5.4.0 (różni się jedynie wartością, którą reprezentuje `PIN` w celu odróżnienia wersji). 

  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites
(edytowany)

Sprawdziłem cały kod i jeśli wywalę ten z const to działa. Czy mogę const usunąć i INT zostawić ? Nie rozsypie się coś?

Edytowano przez hazi18
  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites

Zaraz zaraz...

Kod pierwszy:

extern void digitalWrite(int, int);

#define PIN 2

int main(void)
{
    digitalWrite(PIN,1);    
}

Kod drugi:

extern void digitalWrite(int, int);

const int PIN =  2;

int main(void)
{
    digitalWrite(PIN,1);    
}

I teraz:

$ avr-gcc -Os -S -o k1.s k1.c
$ avr-gcc -Os -S -o k2.s k2.c
$ diff k1.s k2.s
1c1
< 	.file	"k1.c"
---
> 	.file	"k2.c"
23a24,29
> .global	PIN
> 	.section	.rodata
> 	.type	PIN, @object
> 	.size	PIN, 2
> PIN:
> 	.word	2
24a31
> .global __do_copy_data

Gdzie to takie same? Co robię źle?

  • Lubię! 2

Udostępnij ten post


Link to post
Share on other sites

Przetestowałem z Arduino 1.8.9 i kod jest identyczny. Więc pewnie sporo zależy od wersji kompilatora użytych ustawień itd.

No i wypadałoby chyba porównywać kod programu, a nie coś pośredniego.

 

W każdym razie kod z const jest bardzo łatwo popsuć, dlatego dawno temu polecano używanie #define. Obecnie moda się zmieniła i fanatycy C++ preferują const.

Od czasów C++11 dostępny jest operator constexpr, który działa z moją wersją Arduino - może to pogodzi obie strony?

  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites
(edytowany)

Okej, w przypadku C @ethanak ma rację - const daje nam dodatkowy bagaż.

Ale w przypadku gdy nasz kod jest w C++:

avr-gcc -Os -S -o k1.s k1.cpp
avr-gcc -Os -S -o k2.s k2.cpp
diff k1.s k2.s
1c1
< 	.file	"k1.cpp"
---
> 	.file	"k2.cpp"

* sprawdzone na $avr-gcc -v -> gcc version 5.4.0 (GCC)

** https://godbolt.org/ kompiluje .cpp - dlatego wynikowy assembler był taki sam

Edytowano przez Matthew11
  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites

@Matthew11 możesz porównać wyniowy kod progamu, a nie to co wychodzi po kompilacji samych plików? Bo u mnie linker wyrzuca nieużywaną sekcję, więc pliki .o mają dodatkowy bagaż, ale docelowy elf już nie.

Dodatkowy test dla ciekawskich - dodanie static do const. Wtedy każdy kompilator powinien już dać poprawny wynik, nawet w plikach pośrednich.

Edit: Coś co mnie dla odmiany zaciekawiło - użycie avr-g++ zamiast avr-gcc daje inny kod i wtedy oba pliki są identyczne nawet na etapie plików obiektowych... Może dlatego tylko entuzjaści C++ lubią wersję z const 🙂

  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites
12 minut temu, Elvis napisał:

dodanie static do const

No ale to już jest naciąganie - sztuczne tworzenie zmiennych statycznych. Jeśli program składa się z więcej niż jednego pliku (co jest raczej normalne przy czymś poważniejszym niż "Hello world") tworzenie identycznych statycznych zmiennych nie jest raczej dobrą praktyką. Owszem - można w jakimś pliku "cośtam.h" zdefiniować "static const int cośtam = ileśtam" i inkludować go gdzie się da - ale czy nie jest to po prostu próba ominięcia #define (które w pojęciu zatwardziałych cdwuplosowców jest be i fujaste i powinno wylecieć z języka)?

  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites
(edytowany)

Sprawdziłeś zawartość plików wynikowych jak napisałem wcześniej, czy ciągle porównujesz tylko pliki .o ?

Edytowano przez Elvis
  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites

Proponuję przeprowadzić następujący test:

Po pierwsze należy porównywać programy wynikowe, a nie efekty pośrednie działania kompilatora. Wobec tego przykłady należy trochę zmienić - żeby się kompilowały i nie były puste:

test1.c


void digitalWrite(int a, int b)
{
        volatile int xa = a;
        volatile int xb = b;
}

#define PIN 2

int main(void)
{
    digitalWrite(PIN,1);
}

test2.c

void digitalWrite(int a, int b)
{
        volatile int xa = a;
        volatile int xb = b;
}

const int PIN =  2;

int main(void)
{
    digitalWrite(PIN,1);
}

Kompilacja:

avr-gcc -Os -c test1.c 
avr-gcc -Os -c test2.c 
avr-gcc -Os -Wl,--gc-sections -mmcu=atmega328p -o test1.elf test1.o
avr-gcc -Os -Wl,--gc-sections -mmcu=atmega328p -o test2.elf test2.o

Teraz można porównać co wyszło:

elvis@raider:~/blink_test/test$ avr-size test1.elf 
   text	   data	    bss	    dec	    hex	filename
    178	      0	      0	    178	     b2	test1.elf
elvis@raider:~/blink_test/test$ avr-size test2.elf 
   text	   data	    bss	    dec	    hex	filename
    200	      0	      0	    200	     c8	test2.elf

Jak widać po danych nie ma nawet śladu, ani w segmencie data, ani bss. Co ciekawe wersja z const daje większy plik wynikowy - ale to już taki urok kompilatora 😞

Można zawsze wygnerować faktycznie użyte pliki i popatrzeć czym się różnią:

elvis@raider:~/blink_test/test$ avr-objdump -d test1.elf > test1.s
elvis@raider:~/blink_test/test$ avr-objdump -d test2.elf > test2.s

Po stałej PIN w obu przypadkach nie ma śladu, natomiast faktycznie kompilator trochę gorzej się zachował po zobaczeniu const. W każdym razie używanie const nie powoduje zużycia pamięci RAM, ta zmienna, która była widoczna w plikach .o jest usuwana przez linker, bo nie jest do niczego potrzebna.

  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites

No to troszkę inaczej, czyli bliżej rzeczywistości.

Plik dw.cpp identyczny w obu przypadkach:

void digitalWrite(int pin, int value)
{
    volatile int a = pin;
    volatile int b = value;
}

Zestaw pierwszy:

plik sc1.cpp:

#include "testy1.h"

const int PIN = 7;
int main(void)
{
    pinOn();
}

plik sd1.cpp:

#include "testy1.h"

void pinOn(void)
{
    digitalWrite(PIN,1);
}

plik testy1.h:

extern void pinOn(void);
extern const int PIN;
extern void digitalWrite(int, int);

Zestaw drugi:

plik sc2.cpp:

#include "testy2.h"

int main(void)
{
    pinOn();
}

plik sd2.cpp:

#include "testy2.h"

void pinOn(void)
{
    digitalWrite(PIN,1);
}

plik testy2.h:

extern void pinOn(void);
#define PIN 7
extern void digitalWrite(int, int);

I teraz kompilacja:

$ avr-gcc -Os -o s1.elf sc1.cpp sd1.cpp dw.cpp
$ avr-gcc -Os -o s2.elf sc2.cpp sd2.cpp dw.cpp
$ avr-size s1.elf
   text	   data	    bss	    dec	    hex	filename
     80	      2	      0	     82	     52	s1.elf
$ avr-size s2.elf
   text	   data	    bss	    dec	    hex	filename
     52	      0	      0	     52	     34	s2.elf

Jak widać, w przypadku const kod jest dłuższy, a w sekcji data mamy naszego pina 🙂

Przypadek ze static pominąłem.

 

  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites

Zapomniałeś o opcji dla linkera - bez niej nieużywane śmieci zostają w pamięci.

  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Gość
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.


×
×
  • Utwórz nowe...