Skocz do zawartości

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


ethanak

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
Link do komentarza
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
Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

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
Link do komentarza
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
Link do komentarza
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
Link do komentarza
Share on other sites

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
Link do komentarza
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
Link do komentarza
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
Link do komentarza
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
Link do komentarza
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
Link do komentarza
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!

Anonim
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...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.