Forum: Compiler & IDEs C++: Interessanter Effekt bei constexpr Arrays


von Wilhelm M. (wimalopaan)


Lesenswert?

Hallo zusammen,

ich bin vor ein paar Tagen über einen "witzigen" Effekt gestolpert. 
Irrigerweise hatte sich bei mir die Vorstellung festgesetzt, dass der 
Compiler immer(!) constexpr-Ausdrücke in den generierten Code übernimmt 
und deswegen für z.B. constexpr Objekte kein RAM benötigt. Schließlich 
kann ich ja zur Compile-Zeit mit Hilfe von constexpr-Funktionen ganze 
Container sortieren, ohne ein einziges Byte zu belegen, etc.

Im folgenden Beispiel ist das auch so: das Data-Segment ist 0-Bytes 
lang.
1
#include <stdint.h>
2
3
volatile uint8_t x = 0;
4
volatile uint8_t y = 0;
5
6
constexpr uint16_t size = 4;
7
constexpr uint16_t offset = 1;
8
9
int main() {
10
    constexpr const uint8_t array[size + offset] = {1, 2, 3, 4};
11
    
12
    y = array[x];
13
    
14
    while(true) {}
15
}

Der Compiler erzeugt ein "assembler-switch-case".

Macht man allerdings
1
constexpr uint16_t offset = 1;
so erhält man ein Data-Segment von 4 Bytes.

Diesen Effekt kann man generell beobachten: das Array muss immer um 
mindestens 1 länger sein als die Anzahl der Initializer. Auch ein einem 
std::array<>:
1
#include <stdint.h>
2
#include "std/array.h"
3
4
volatile uint8_t x = 0;
5
volatile uint8_t y = 0;
6
7
constexpr uint16_t size = 250;
8
constexpr uint16_t offset = 1;
9
10
int main() {
11
    constexpr const auto array = [](){
12
        std::array<uint8_t, size + offset> a;
13
        for(std::remove_cv_t<decltype(size)> i = 0; i < size; ++i) {
14
            a[i] = i + 1;
15
        }
16
        return a;
17
    }();
18
    
19
    y = array[x];
20
    
21
    while(true) {}
22
}

Ist hier offset >= 1, bekommen wir wieder 0 Bytes im Data-Segment, aber 
2456 Bytes(!) Code (atmega328). Bei offset == 0 sind es eben 250 Bytes 
Data-Segment.
(Die Initialisierung per IIFE ist nur deswegen im o.g. Beispiel da, 
damit der Compiler auch eine lange Sprungtabelle generieren muss.)

Selbstverständlich ist das alles konform, doch hatte ich damit, nämlich 
Platz im Data-Segment, nicht gerechnet, sondern mit einer Abwicklung im 
Code...

:
von Little B. (lil-b)


Lesenswert?

Welcher compiler?

Ich kann das so nicht nachvollziehen.

Ich habe dein erstes Beispiel selbst durchkompiliert
arm-none-eabi-gcc
GNU Tools ARM Embedded 4.9 2015q3
für cortex m-3

mit offset = 0 ist sizeof(main) == 0x30
mit offset = 1 ist sizeof(main) == 0x3C

In beiden Fällen ist sizeof(data) == 0

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wilhelm M. schrieb:
> so erhält man ein Data-Segment von 4 Bytes.

Auch mit -fno-tree-switch-conversion?

von Yalu X. (yalu) (Moderator)


Lesenswert?

Bei mir legt der GCC (6.3.0 für AVR bzw. 6.3.1 für x86) in beiden Fällen
ein temopräres Array auf dem Stack an. Deswegen wird dafür kein Platz
in der Data-Section benötigt.

von Wilhelm M. (wimalopaan)


Lesenswert?

Ich habe den Unterschied hier ab g++ >= 7.0.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wilhelm M. schrieb:
> doch hatte ich damit, nämlich Platz im Data-Segment, nicht gerechnet,
> sondern mit einer Abwicklung im Code...

Weil die angelegte "artificial" Variable (von gcc angelegt) in .rodata 
liegt, und das nun mal Teil des RAM ist.  Im gegensatz von artificial 
Variablen, die tree-switch-conversion anlegt, kann dieses Vergalten 
nicht durch Schalter unterbunden werden.  Und die Artificials im Flash 
anlegen geht hier auch nicht, da Named Address Spaces kein Teil von C++ 
sind :-)

von Wilhelm M. (wimalopaan)


Lesenswert?

Leider habe ich bisher aber eben keine Erklärung gefunden, dass das 
Verhalten von der full/partial initialization abhängt!

Leider findet keine Umsetzung als Verzweigungsmonster statt sondern es 
wird auf dem Stack eine Lookuptabelle (zur Laufzeit!!!) zusammen gebaut 
...

Ist aber auch nicht so wichtig, weil große ro-Arrays eben bei C++ als 
generischer Typ (template) ins PGM, o.ä. wandern und der Zugriff darauf 
gekapselt ist - wie auch bei String-Literalen. Insofern alles kein 
Problem.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wilhelm M. schrieb:
> Leider habe ich bisher aber eben keine Erklärung gefunden, dass das
> Verhalten von der full/partial initialization abhängt!

Zumindest teilweise ist das Verhalten von den Kosten abhängig, z.B. -O2 
vs. -Os.

> Leider findet keine Umsetzung als Verzweigungsmonster statt sondern es
> wird auf dem Stack eine Lookuptabelle (zur Laufzeit!!!) zusammen gebaut
> ...

Ist ja zunächst das, was du hingeschrieben hast :-)

Hilfreich ist das array static zu machen, weil so zumindest die Kopie 
auf den Stack vermieden wird, die alleine ja schon übel ist.  Allerdings 
liegt das Array dann bei avr-gcc natürlich im RAM (.rodata), aber das 
tut es bei einem artificial Array auch, nur dass es da nicht explizit in 
der Quelle steht.

Beitrag #7283954 wurde von einem Moderator gelöscht.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.