C++: Interessanter Effekt bei constexpr Arrays

#4920787
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...
#4920867
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
Persönliche Seite #4922391
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 :-)
#4922412
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.
Persönliche Seite #4922461
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.

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