Wie Verwendung von Konstanten erzwingen?

Gast #6653716
Lesenswert?

Wie kann ich die Verwendung von Konstanten erzwingen?

Mein Beispielcode:
float a,b;
const float Winkel = 45.0;
a = b * sin(Winkel / 360  2  PI);

Worum es mir geht, dass der uC zur Laufzeit nicht jedes mal den Sinus 
von 45° ausrechnen muss, sondern dass der Compiler einfach eine 
Konstante 0.707106781 dafür einsetzt. Klar, ich kann das während der 
Quellcodeeingabe einfach selber mit dem Taschenrechner ausrechnen, will 
das aber nicht! Sinus ist auch nur ein Beispiel, genau so gut könnte da 
auch log oder atn stehen.
Moderator Persönliche Seite #6654105
Lesenswert?

Ben S. schrieb:
> Das macht der Compiler bereits für dich, ohne dass du es weißt - sofern
> du Optimierung eingeschaltet hast.

Für den gcc ist das korrekt. Fragt sich nur, ob der TO überhaupt gcc 
verwendet oder gar die Frage ganz allgemein für alle üblichen C-Compiler 
stellt. Er verrät ja auch nicht den µC.

Code:
1
#include <math.h>
2
#include <stdio.h>
3

4
int main ()
5
{
6
    float a,b;
7
    const float Winkel = 45.0;
8

9
    b = 1; // Initialisierung hat der TO vergessen
10
    a = b * sin(Winkel / 360.0 * 2.0 * M_PI); // M_PI statt PI
11

12
    printf ("%f\n", a);
13
}

Unter Linux übersetzt:
1
$ cc -O -S sin.c && cat sin.s
2
        .file   "sin.c"
3
        .section        .rodata.str1.1,"aMS",@progbits,1
4
.LC1:
5
        .string "%f\n"
6
        .text
7
        .globl  main
8
        .type   main, @function
9
main:
10
.LFB14:
11
        .cfi_startproc
12
        subq    $8, %rsp
13
        .cfi_def_cfa_offset 16
14
        movsd   .LC0(%rip), %xmm0
15
        leaq    .LC1(%rip), %rdi
16
        movl    $1, %eax
17
        call    printf@PLT
18
        movl    $0, %eax
19
        addq    $8, %rsp
20
        .cfi_def_cfa_offset 8
21
        ret
22
        .cfi_endproc
23
.LFE14:
24
        .size   main, .-main
25
        .section        .rodata.cst8,"aM",@progbits,8
26
        .align 8
27
.LC0:
28
        .long   1610612736
29
        .long   1072079006
30
        .ident  "GCC: (Debian 6.3.0-18+deb9u1) 6.3.0 20170516"
31
        .section        .note.GNU-stack,"",@progbits

Wenn der Optimizer eingeschaltet ist, muss man noch nichtmals die 
math-lib libm dazulinken:
1
$ cc -O sin.c -o sin && ./sin
2
0.707107

Die Sinus-Funktion wird also gar nicht aufgerufen - wie man aus dem 
Assembler-Listing auch erkennen kann. Sonst stünde da ein
1
call    sin@PLT
im Assembler-Code.

Ohne Optimizer:
1
$ cc sin.c -o sin && ./sin
2
/tmp/ccyuMhzd.o: In function `main':
3
sin.c:(.text+0x4e): undefined reference to `sin'
4
collect2: error: ld returned 1 exit status

Hier fehlt dann die Option "-lm", also:
1
$ cc sin.c -o sin -lm && ./sin
2
0.707107

Ohne die Optimierung wird demnach die Funktion sin() aus libm benötigt.
#6654246
Lesenswert?

Hallo,

ich verstehe das Problem nicht. Wenn 'const Winkel' in main() berechnet 
wird, dann wird 'Winkel' automatisch in while(1) zur Laufzeit überall 
verwendet.

Etwas anderes wäre es wenn man im Laufzeitcode überall die 
Berechnungsfunktion verwendet/aufruft statt der schon berechneten const 
Variablen. Dann kommt es auf den Compiler an wie er optimieren kann.
#6654593
Lesenswert?

Hallo,

gut, dann war ich zum lesen schon zu müde. Dann beziehe ich mich nicht 
auf 'Winkel' sondern auf 'a'. Meine Aussage bleibt die Gleiche. Wenn a 
als Konstante initialisiert wird, dann kann und wird diese zur Laufzeit 
verwendet. Denn das ist kein Funktionsaufruf der an anderer Stelle 
wiederholt werden könnte sondern eine Initialisierung.
1
const float Winkel = 45.0;
2
const float b = 1.0;
3
const float a = b * sin(Winkel / 360 * 2 * M_PI);
Gast #6654606
Lesenswert?

Veit D. schrieb:
> Meine Aussage bleibt die Gleiche. Wenn a als Konstante initialisiert
> wird, dann kann und wird diese zur Laufzeit verwendet

Natürlich. Sie wird aber auch zur Laufzeit initialisiert, mit echter 
Berechnung des sin. Wenigstens im Default.

Du verwechselt anscheinend häufiger C und C++.

Hier steht die Sprache zwar nicht explizit, es wurde aber mehrfach 
darauf hingewiesen, da C++ das Problem nicht hat.
#6655650
Lesenswert?

Sven B. schrieb:
> Das einzige mir bekannte Konstrukt, was dir das garantiert (abgesehen
> davon, einfach ein Literal hinzuschreiben), ist consteval aus C++.

Mit consteval deklariert man immediate-functions: diese müssen zur 
Compilezeit ausgewertet werden.

Mit constexpr deklariert man Funktionen, die in einem constexpr-Kontext 
ausgewertet werden können, wenn es möglich ist, d.h. ihr Argumente 
ebenfalls constexpr sind.

Constexpr-"Variablen" sind Variablen, deren Initialisierung zur 
Compilezeit bestimmt wird, entweder durch constexpr (falls möglich) oder 
consteval Funktionen. Das Verwenden einer solchen "Variablen" ist als 
eine constant-expression. Übeall dort, wo sie eingesetzt wird, für das 
zur einer constant-expression (wie ein Literal) und der Compiler wird 
dasselbe damit machen wie mit einem Literal. Das führt im Endeffekt 
dazu, dass die constexpr-"Variablen" keine Variablen (benannte Objekte) 
mehr sind und deswegen sich nicht materialisieren.
Gast #6655697
Lesenswert?

Wilhelm M. schrieb:
> Das führt im Endeffekt dazu, dass die constexpr-"Variablen" keine
> Variablen (benannte Objekte) mehr sind und deswegen sich nicht
> materialisieren.

Das gilt doch prinzipiell auch für const Variablen, die zur kompilezeit 
bzw. Linkzeit bekannt sind, also wie beim TO. Nur das es halt keinen 
Fehler gibt, wenn das nicht der Fall ist.
#6655801
Lesenswert?

A. S. schrieb:
> Wilhelm M. schrieb:
>> Das führt im Endeffekt dazu, dass die constexpr-"Variablen" keine
>> Variablen (benannte Objekte) mehr sind und deswegen sich nicht
>> materialisieren.
>
> Das gilt doch prinzipiell auch für const Variablen, die zur kompilezeit
> bzw. Linkzeit bekannt sind, also wie beim TO. Nur das es halt keinen
> Fehler gibt, wenn das nicht der Fall ist.

Ja.

constexpr bedeutet const, und const (bei global bzw. namespace) bedeutet 
internal-linkage. Solange man nicht die Adresse bestimmt, wird nicht 
materialisiert.

extern const kann man zwar schreiben und ist dann eine Deklaration. Die 
wieder kann sich nur auch eine vorher definiertes const-Objekt beziehen 
(e.g. im Header).

Mit mehrfachen const-Objekte bekommt man also in unterschiedlichen TU 
unterschiedliche Adresse für const.

constexpr ist implizit inline. Hier bekommt man dieselbe Adresse. Das 
kann man mit const auch machen, dann als inline const etwa in Headern.

Ja, und wie schon gesagt, constexpr Objekte erzwingen ein Compile-Zeit 
Initialisierung, const nicht.
#6656807
Lesenswert?

Um jetzt etwas pingelig zu werden:
In C und C++ heißt const genau genommen nicht, daß ein Objekt konstant 
ist.
Sondern nur, daß man es im Geltungsbereich des const nicht ändert.

Beispiel:
  const volatile  int i = 0;
heißt: hier wird i nicht mehr geändert, aber in einem anderen Thread 
oder einem Signalhandler, Interrupt o.ä. kann es geändert werden. Dort 
muß es dann ohne const deklariert werden natürlich.
Gast #6657033
Lesenswert?

Wilhelm M. schrieb:
> const == read-only

Für C++ heißt const auch konstant. Zumindest bei const int x=3;

Und wie bitte wird in C bei

Klaus W. schrieb:
> Beispiel:
>   const volatile  int i = 0;
> heißt: hier wird i nicht mehr geändert, aber in einem anderen Thread
> oder einem Signalhandler, Interrupt o.ä. kann es geändert werden.

i ins RAM gelegt wird?
Gast #6657275
Lesenswert?

Wilhelm M. schrieb:
> A. S. schrieb:
>> Für C++ heißt const auch konstant.
>
> Nein. Und das Beispiel mit const volatile hast Du ja selbst geliefert.
>
> Oder
>
1
> int x;
2
> const int& rx = x;
3
>
>
> Hier ist ganz klar ersichtlich, dass const hier read-only bedeutet.

Wilhelm, Du hast Klaus Behauptung

Klaus W. schrieb:
> C++ heißt const genau genommen nicht, daß ein Objekt konstant
> ist.

bestätigt mit

Wilhelm M. schrieb:
> const == read-only

Natürlich gibt es Fälle, wie Dein Beispiel, wo es nur Read-Only 
bedeutet.

Hier im Forum verwechseln Leute häufig den Fundamentalen Unterschied 
eines Objektes mit const in C und C++: In C nur Read-Only, in C++ jedoch 
konstant.

Dass bei einem Zeiger oder einer Referenz etwas anderes gilt, bleibt da 
unbenommen. Ein Zeiger auf ein Objekt ist kein Objekt.
#6657460
Lesenswert?

A. S. schrieb:
> Dass bei einem Zeiger oder einer Referenz etwas anderes gilt, bleibt da
> unbenommen. Ein Zeiger auf ein Objekt ist kein Objekt.

Aber das, worauf der Zeiger zeigt, ist ein Objekt.
Und auf dasselbe Objekt kann auch in C++ ein Zeiger zeigen, der es als 
const sieht, und ein anderer, über den es geändert werden kann.

A. S. schrieb:
> Klaus W. schrieb:
>> Beispiel:
>>   const volatile  int i = 0;
>> heißt: hier wird i nicht mehr geändert, aber in einem anderen Thread
>> oder einem Signalhandler, Interrupt o.ä. kann es geändert werden.
>
> i ins RAM gelegt wird?

Auch in C++ kann ich von einer const-Variablen eine Adresse bilden.
Spätestens dann wird der Compiler die auch im RAM halten.
#6657475
Lesenswert?

A. S. schrieb:
> In C nur Read-Only, in C++ jedoch
> konstant.

Das musst Du mal erklären.

A. S. schrieb:
> Ein Zeiger auf ein Objekt ist kein Objekt.

Eine Variable ist (im engeren Sinn) ein benanntes Objekt - auch eine 
Zeigervariable.

Ein Objekt ist ein Speicherbereich, in dem ein Wert abgelegt ist.

Ein Wert ist eine Bitfolge, die entsprechend einem Datentyp 
interpretiert wird.

Ein Datentyp beschreibt die möglichen Operationen und die möglichen, 
(beobachtbaren) Werte.
#6658539
Lesenswert?

Vielleicht zur allgemeinen Erbauung ein kleines Programm?

Es wird eine const i vereinbart, mit 12 initialisiert, und ein Zeiger 
darauf. Der ist jetzt nicht ein Zeiger auf const i, sondern auf const 
(was man natürlich nicht machen sollte, aber der Compiler verwehrt es 
nicht.

Man hat also im RAM nur eine int-Variable.

Die wird zweimal ausgegeben, erst als i selbst und dann über den Zeiger.

Über den Zeiger wird sie dann zu 42 geändert und wieder auf beide Arten 
ausgegeben.
1
#include <iostream>
2

3
void zeigeInt( const char *kommentar, const int i )
4
{
5
  std::cout << kommentar << i << std::endl;
6
}
7

8
int main( int nargs, char **args )
9
{
10
  const  int     i = 12;
11

12
  int           *non_const_ptr_i = const_cast<int*>(&i); // böse, aber nicht verboten
13

14
  zeigeInt( "vorher i direkt      ", i );
15
  zeigeInt( "vorher ueber Zeiger  ", *non_const_ptr_i );
16
  *non_const_ptr_i = 42;
17
  zeigeInt( "nachher i direkt     ", i );
18
  zeigeInt( "nachher ueber Zeiger ", *non_const_ptr_i );
19

20
  return 0;
21
}
1
klaus@lap7:~$ g++ -Wall -std=c++14 a.cpp -o a && ./a
2
vorher i direkt      12
3
vorher ueber Zeiger  12
4
nachher i direkt     12
5
nachher ueber Zeiger 42
6
klaus@lap7:~$ g++ -Wall -std=c++17 a.cpp -o a && ./a
7
vorher i direkt      12
8
vorher ueber Zeiger  12
9
nachher i direkt     12
10
nachher ueber Zeiger 42
11
klaus@lap7:~$ g++ -Wall -O3 -std=c++0x a.cpp -o a && ./a
12
vorher i direkt      12
13
vorher ueber Zeiger  12
14
nachher i direkt     12
15
nachher ueber Zeiger 42
16
klaus@lap7:~$


Weil der böse cast zu undefiniertem Verhaöten führt, kann das Ergebnis 
mit einem anderen Compiler theoretisch auch anders aussehen.

Aber zumindest sieht man hier, was der gcc sich dabei denkt:
Die Variable i ist als const deklariert, also denkt sich der gcc, daß 
sie sich nicht ändert und nimmt bei der Verwendung des Werts gar nichts 
aus dem Speicher, sondern setzt gleich direkt den Wert 12 jeweils ein.

Weil ich aber die Adresse von i verwende, ist er gezwungen, tatsächlich 
auch eine int-Variable anzulegen (was er sonst zumnindest mit 
Optimierung weglassen würde).

Über diese Adresse kann ich in der zweiten Ausgabe ebenfalls 12 sehen,

Ebenfalls über die Adresse ändere ich den Wert zu 42, und gebe wieder 
auf beide Arten aus. Bei i nimmt der Code gar nicht den Wert aus dem 
Speicher ("ist ja const..."), sondern der Compiler setzt wieder gleich 
die vermutete 12 ein.
Über den Zeiger gebe ich dagegen den wirklichen Wert 42 aus.

Interessant: ändere ich i von const i zu const volatile i, dann verlässt 
sich der Compiler nicht mehr auf das const, sondern macht es richtig:
1
#include <iostream>
2

3
void zeigeInt( const char *kommentar, const int i )
4
{
5
  std::cout << kommentar << i << std::endl;
6
}
7

8
int main( int nargs, char **args )
9
{
10
  const volatile  int     i = 12;
11

12
  int           *non_const_ptr_i = const_cast<int*>(&i); // böse, aber nicht verboten
13

14
  zeigeInt( "vorher i direkt      ", i );
15
  zeigeInt( "vorher ueber Zeiger  ", *non_const_ptr_i );
16
  *non_const_ptr_i = 42;
17
  zeigeInt( "nachher i direkt     ", i );
18
  zeigeInt( "nachher ueber Zeiger ", *non_const_ptr_i );
19

20
  return 0;
21
}
1
klaus@lap7:~$ g++ -Wall -O3 -std=c++0x a.cpp -o a && ./a
2
vorher i direkt      12
3
vorher ueber Zeiger  12
4
nachher i direkt     42
5
nachher ueber Zeiger 42

Mir ist klar, daß man so nicht programmieren soll.
Aber vielleicht erhellt es etwas die Diskussion um const.
#6658578
Lesenswert?

Wilhelm M. schrieb:
> Das führt im Endeffekt dazu, dass die constexpr-"Variablen" keine Variablen
> (benannte Objekte) mehr sind und deswegen sich nicht materialisieren.

Im Sine der Sprache sind sie immer noch Variablen.

Wilhelm M. schrieb:
> A. S. schrieb:
>> Für C++ heißt const auch konstant.
>
> Nein. Und das Beispiel mit const volatile hast Du ja selbst geliefert.
>
> Oder
> int x;
> const int& rx = x;
>
> Hier ist ganz klar ersichtlich, dass const hier read-only bedeutet.

rx ist aber kein Objekt.

A. S. schrieb:
> Dass bei einem Zeiger oder einer Referenz etwas anderes gilt, bleibt da
> unbenommen. Ein Zeiger auf ein Objekt ist kein Objekt.

Ein Zeiger ist schon ein Objekt. Aber das const bezieht sich in nicht 
auf den Zeiger. Ein const int* ist kein konstanter Zeiger, sondern ein 
Zeiger, über den man das Objekt, auf das er zeigt, nicht ändern kann. 
Das sagt weder etwas über die Konstantheit des Zeigers aus, noch etwas 
darüber, ob das Zielobjekt selbst eine Konstante ist.

Klaus W. schrieb:
> A. S. schrieb:
>> Klaus W. schrieb:
>>> Beispiel:
>>>   const volatile  int i = 0;
>>> heißt: hier wird i nicht mehr geändert, aber in einem anderen Thread
>>> oder einem Signalhandler, Interrupt o.ä. kann es geändert werden.
>>
>> i ins RAM gelegt wird?
>
> Auch in C++ kann ich von einer const-Variablen eine Adresse bilden.
> Spätestens dann wird der Compiler die auch im RAM halten.

Er muss sie so oder so ins RAM legen, wenn sie volatile ist.

A. S. schrieb:
> Wilhelm M. schrieb:
>> A. S. schrieb:
>>
>>> In C nur Read-Only, in C++ jedoch
>>> konstant.
>>
>> Das musst Du mal erklären
>
> const int x = 2;
>
> In C++ kann ich das x als Konstante verwenden, z.b. int a[x];
>
> X ist konstant, eine Konstante.

Ja. In C++ gibt es allerdings zwei Arten von Initialisierung, nämlich 
statische und dynamische. Statische Initialisierung erfolgt zur 
Compilezeit, dynamische zur Laufzeit. In C könnten Objekte mit 
statischer Lebensdauer auch nur statisch initialisiert werden, während 
diese Einschränkung in C++ nicht besteht. Dein x kann hier verwendet 
werden, weil es statisch initialisiert wurde. Würde es dynamisch 
initialisiert, z.B. durch einen Funktionsaufruf, dann würde das nicht 
mehr gehen, obwohl x const ist.

Wilhelm M. schrieb:
>> A. S. schrieb:
>>
>>> In C nur Read-Only, in C++ jedoch
>>> konstant.

In C++ konstant oder nur read-only, je nach Kontext.

> A. S. schrieb:
>> In C++ kann ich das x als Konstante verwenden, z.b. int a[x];
>
> Und die meinst, das geht in C nicht?

Es geht in C nur, wenn das Array lokal ist und VLAs unterstützt werden. 
Sonst geht es nicht, da eben nur konstante Ausdrücke als Array-Größen 
verwendet werden dürfen und x kein solcher ist.

> A. S. schrieb:
>> X ist konstant, eine Konstante.
>
> Nö:
> [c]
> *const_cast<int*>(&x) = 42;
> [c]

Das hat undefiniertes Verhalten, und es wird in der Praxis auch oft 
nicht so funktionieren, wie du dir das denkst.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren