Hallo!
Ich mache zur Zeit einen Kurs in der Programmiersprache C und habe einen
ziemlich kleinkarierten Lehrer.
Neulich haben wir die Datentypen durchgenommen. Anschließend kamen die
Konstanten dran.
Nun meine Frage:
Wo liegt der Unterschied zwischen einer konstanten Variablen (const int
xyz;) und der Präprozessordirektive (#define ) um Konstanten zu
definieren?
Der Lehrer meinte, dass die #define-Variante, um eine Konstante zu
definieren, wohl heutzutage nicht mehr genutzt wird.
-Sieht man allerdings einige Listings hier im Forum, so würde ich
wetten, dass die #define-Variante doch häufiger anzutreffen ist.
Warum wird sie "heutzutage nicht mehr genutzt"/wo liegen die
Unterschiede?
Vielen Dank!
Naja #define ist nur eine Textersetzung.
Bei ner const ist halt ne Typenprüfung mit dabei.
Also "const ui8_t test = 583" wird ne Warning werfen.
Wobei ich z.B. noch nie consts verwendet hab, sondern immer nur defines.
marjus schrieb:
> Wo liegt der Unterschied zwischen einer konstanten Variablen (const int> xyz;) und der Präprozessordirektive (#define ) um Konstanten zu> definieren?
"const" sind Variablen, die nicht verändert werden dürfen. Nicht
geeignet als lexikalische Konstanten, also nicht äquivalent zu Zahlen.
Folglich ist
const int N = 10;
int array[N];
in C nicht möglich.
> Der Lehrer meinte, dass die #define-Variante, um eine Konstante zu> definieren, wohl heutzutage nicht mehr genutzt wird.
Da liegt er völlig falsch. Es gibt in C weiterhin keine Alternative. In
C++ sieht das völlig anders aus, da ist "const" anders definiert.
Mit const int wird einer Konstante ein Speicherplatz zugewiesen. Bei
#define wird der Wert direkt per Opcode, also direkt in dem
auszuführenden Programm abgelegt.
Die Vorteile von const sind.
- Es gibt eine feste Speicherstelle, auf die auch mit einem Pointer
zugegriffen werden kann.
- Die Konstante wird nur einmal im Speicher gehalten, wogegen Konstanten
per #define bei jedem Aufruf wieder als Wert im Opcode auftauchen.
- Wenn man die Konstante mit const im RAM-Bereich ablegt, kann man sie
beim Debuggen verändern, wenn man möchte.
Man kann sich das so erklären.
#define sind praktisch Makros, die vor dem eigentlichen Compilerlauf,
also vom Präprozessor, im Source-Code ausgetauscht werden.
const legt eine Variable an.
Man braucht also Speicherplatz dafür und man braucht Code, um die
Variable aus dem Speicher zu laden.
#define ist eine Textersetzung im Präprozessor.
Es wird dann jedes Auftreten des define-Namens durch die Zahl ersetzt.
D.h. es wird kein Speicherplatz benötigt und es wird auch weniger Code
erzeugt.
Ein Register kann direkt mit der Zahl geladen werden, ein Vergleich
direkt mit der Zahl erfolgen.
In der MC-Programmierung ist deswegen const recht ungebräuchlich.
Peter
Peter Dannegger schrieb:
> In der MC-Programmierung ist deswegen const recht ungebräuchlich.
Es ist durchaus gebräuchlich, aber eben nicht für lexikalische
Konstanten, sondern für Daten/Tabellen/Strings die ins ROM sollen.
A. K. schrieb:
> Folglich ist> const int N = 10;> int array[N];> in C nicht möglich.
Jein. In C99 ist es möglich, wenn es sich innerhalb einer Funktion
befindet -- allerdings nur deshalb, weil es dann dort als "variably
modified array" gilt.
Peter Dannegger schrieb:
> const legt eine Variable an.> Man braucht also Speicherplatz dafür und man braucht Code, um die> Variable aus dem Speicher zu laden.
Bevor das hier gebetsmühlenartig wiederholt wird: das ist in der Form
auch nicht korrekt. Es kann Speicherplatz und Code dafür benötigt
werden, aber es muss nicht. Gerade das genannte
1
constintN=10;
ist ein guter Kandidat, bei dem die Variable N vom Optimierer
weggeworfen wird, und die Zahl 10 stattdessen im Assemblercode als
Direktoperand landet.
Jörg Wunsch schrieb:
> vom Optimierer> weggeworfen wird, und die Zahl 10 stattdessen im Assemblercode als> Direktoperand landet.
Yep, inlinen kann er, aber solange das Dings global ist wird der
Compiler nicht drum herum kommen, dafür Platz vorzusehen, weil eben auch
exportiert.
A. K. schrieb:
> Jörg Wunsch schrieb:>>> vom Optimierer>> weggeworfen wird, und die Zahl 10 stattdessen im Assemblercode als>> Direktoperand landet.>> Yep, inlinen kann er, aber solange das Dings global ist wird der> Compiler nicht drum herum kommen, dafür Platz vorzusehen, weil eben auch> exportiert.
Jein.
Wenn so was als globale Konstante benötigt wird, egal ob in einem Header
File oder File-global, dann macht man das als
Peter Dannegger schrieb:
> const legt eine Variable an.> Man braucht also Speicherplatz dafür und man braucht Code, um die> Variable aus dem Speicher zu laden.
Der Compiler muss aber keine Variable anlegen, sofern nicht auf die
Adresse des const-Objekts zugegriffen wird.
Auf der anderen Seite brauche ich u.U. natürlich auch Speicherzugriffe
um einen durch #define erzeugten Wert aus dem Speicher zu holen, wenn er
nicht als immediate darstellbar ist.
Leider lässt es sich nicht pauschal sagen, dass das eine dem anderen
überlegen ist.
Gruß
Marcus
http://www.doulos.com/arm/
Karl heinz Buchegger schrieb:
> und dann darf der Optimizer wieder zuschlagen :-)
Yep, nur stand das "static" grad eben noch nicht drin als ich das
kommentierte ;-). Ausserdem kommt mir der Begriff "File global" ein
bischen ungewohnt vor, ich kenne das als "file scope" und damit eben
nicht global.
Jörg Wunsch schrieb:
> Es kann Speicherplatz und Code dafür benötigt> werden, aber es muss nicht.
Das ist aber ne völlig andere Baustelle.
Dem Optimierer ist es völlig wurscht, ob die Variable const ist.
Er kann auch normale Variablen wegoptimieren.
Z.B. in Schleifen optimiert er leider oft die Zählvariable weg, was dann
größeren Code erzeugt.
Peter
Marcus Harnisch schrieb:
> Der Compiler muss aber keine Variable anlegen, sofern nicht auf die> Adresse des const-Objekts zugegriffen wird.
Was er aber nur weiss, wenn das Ding eben nicht global ist, sondern wie
grad aufgeführt lokal oder file scope (aka static) ist.
Bei globalen Daten kann er das üblicherweise nicht wissen.
A. K. schrieb:
> Karl heinz Buchegger schrieb:>>> und dann darf der Optimizer wieder zuschlagen :-)>> Yep, nur stand das "static" grad eben noch nicht drin als ich das> kommentierte ;-).
Ich weiß :-)
Drum habe ich es ja auch noch ergänzt.
> Ausserdem kommt mir der Begriff "File global" ein> bischen ungewohnt vor,
Es war noch früh und ich hatte noch keinen Kaffee
Peter Dannegger schrieb:
> Dem Optimierer ist es völlig wurscht, ob die Variable const ist.
Nein. Wenn sie const ist, kann er auch davon ausgehen, dass sie
keinen anderen Wert als den des initializers hat, damit kann er
diesen als immediate eintragen. Das geht bei einer nicht-const-
Variablen nicht, auf die muss er zugreifen.
1
constinti=42;
2
intj=23;
3
4
intreturn_i(void)
5
{
6
returni;
7
}
8
9
intreturn_j(void)
10
{
11
returnj;
12
}
ergibt:
1
.global return_i
2
.type return_i, @function
3
return_i:
4
/* prologue: function */
5
/* frame size = 0 */
6
ldi r24,lo8(42)
7
ldi r25,hi8(42)
8
/* epilogue start */
9
ret
10
.size return_i, .-return_i
11
.global return_j
12
.type return_j, @function
13
return_j:
14
/* prologue: function */
15
/* frame size = 0 */
16
lds r24,j
17
lds r25,j+1
18
/* epilogue start */
19
ret
20
.size return_j, .-return_j
Natürlich tritt in diesem Fall der genannte Effekt ein, dass er für
die Variable i trotzdem Speicherplatz belegt, da sie global ist und
der Compiler daher nicht entscheiden kann, ob andere translation
units sie ggf. noch benötigen. Ich wollte sie aber nicht `static'
machen, denn bei static kann er auch für `j' entscheiden, dass diese
Variable keinen anderen Wert als 23 annehmen kann, und ersetzt die
Konstante dann ebenfalls.
A. K. schrieb:
> Was er aber nur weiss, wenn das Ding eben nicht global ist, sondern wie> grad aufgeführt lokal oder file scope (aka static) ist.>> Bei globalen Daten kann er das üblicherweise nicht wissen.
Auch nur halb richtig. Der Compiler kann innerhalb der /compilation
unit/ den Wert direkt verwenden, falls die const Variable einen
Initialwert hat. Er muss aber trotzdem eine Variable anlegen.
Man sollte sich deshalb davor hüten, initialisierte const Variablen zu
verwenden, die man später durch patchen der image Datei ändern möchte.
Gruß
Marcus
http://www.doulos.com/arm/
Jörg Wunsch schrieb:
> Das geht bei einer nicht-const-> Variablen nicht, auf die muss er zugreifen.
Das sind aber Feinheiten der Optimierung und geht etwas am
ursprünglichen Thema vorbei.
Wenn die Variable nur in der Compile-Unit existiert, ist es ihm wurscht,
ob const oder nicht.
Z.B. hier ne lokale Variable i, die komplett wegoptimiert wurde:
1
uint8_td[8];
2
3
voidtest(uint8_t*s)
4
{
5
56:dc01movwr26,r24
6
58:e0e0ldir30,0x00;0
7
5a:f1e0ldir31,0x01;1
8
uint8_ti;
9
10
for(i=0;i<sizeof(d);i++)
11
d[i]=*s++;
12
5c:8d91ldr24,X+
13
5e:8193stZ+,r24
14
60:81e0ldir24,0x01;1
15
62:e830cpir30,0x08;8
16
64:f807cpcr31,r24
17
66:d1f7brne.-12;0x5c<test+0x6>
18
d[i]=*s++;
19
}
20
68:0895ret
Und mit
--combine -fwhole-program
kann er auch ohne "const" globale Variablen mit nur einer Zuweisung
wegoptimieren.
Peter
Peter Dannegger schrieb:
> Wenn die Variable nur in der Compile-Unit existiert, ist es ihm wurscht,> ob const oder nicht.
Ja, schrieb ich ja. Mit "const" kann er das aber immer wegoptimieren,
was lediglich deine Behauptung widerlegen sollte, dass das "const"
für die Optimierung egal sei. Dass eine äquivalente Optimierung auch
ohne const möglich ist, ist eine andere Frage.
> Es wird dann jedes Auftreten des define-Namens durch die Zahl ersetzt.> D.h. es wird kein Speicherplatz benötigt und es wird auch weniger Code> erzeugt.
Man sollte nicht vergessen dass #define nicht auf Zahlen beschränkt ist.
Man überlege sich einmal folgendes (Horror-)Szenario:
1
#define LONG_MESSAGE "Hallo Welt Hallo Welt Hallo Welt"
2
3
// ...
4
5
void f(char* x)
6
{
7
if (0 == strcmp(LONG_MESSAGE, x))
8
{
9
// ...
10
}
11
}
12
13
// ...
14
void g(char* y)
15
{
16
if (0 == strcmp(LONG_MESSAGE, y))
17
{
18
// ...
19
}
20
}
Hier hätte man genau das Gegenteil erreicht nämlich Speicherplatz
verschwendet.
Leider muss man zu const sagen, dass man die Konstanten (mir etwas
unverständlich) nicht als case Label nutzen kann...
klaus schrieb:
> Leider muss man zu const sagen, dass man die Konstanten (mir etwas> unverständlich) nicht als case Label nutzen kann...
Weil es konstante Variablen sind, so komisch sich das anhört. Wo
Variablen zulässig sind, darf der Compiler auch den Wert einer solchen
konstanten Variablen einsetzen.
In Case-Labels und normalen Array-Indizes (Arrays variabler Länge aus
C99 mal unberücksichtigt) sind Variablen jedweder Art jedoch rein von
der syntaktischen Definition der Sprache her nicht zugelassen, und
deshalb ist es egal ob die "const" sind oder nicht.
Überall dort, wo traditionell nur eine "constant expression" zulässig
ist, müssen konstante Variablen draussen bleiben. In C. Stroustrup war
darüber auch nicht sehr glücklich, weshalb dies in C++ möglich ist.
klaus schrieb:
> Leider muss man zu const sagen, dass man die Konstanten (mir etwas> unverständlich) nicht als case Label nutzen kann...
Solange es sich um Integerwerte handelt, gibt's dafür ja noch die Enums
als dritte Form der Konstantendefinition. Das sind dann wieder "echte"
Konstanten, die man bspw. auch zur Arraydimensionierung verwenden kann.
const wird sehr häufig als "schreibschutz" für variablen verwendet und
macht hier sinn ansonsten würde ich keine Konsatne als const definieren,
siehe z.b. math.h