Forum: Compiler & IDEs static global


von Nicolas S. (Gast)


Lesenswert?

Hallo zusammen,
wenn ich in einer C-Datei eine Variable als "static global" deklariere,
ist die Variable auch aus anderen Modulen verfügbar, wenn ich den Zeiger 
auf die Variablen explizit kopiere. Folgendes funktioniert sinngemäß 
ohne Warnung auf einem ATmega32 mit GCC 4.7.2:
1
// Datei a.c
2
3
// grosser Puffer nimmt schon die Haelfte des SRAMs ein, soll deswegen
4
// halbwegs geschuetzt sein
5
#define BUFSIZE 1024
6
static char sg_buffer[BUFSIZE]; 
7
8
void setbuffer(char val,size_t address) {
9
  address %= BUFSIZE; // Besser an unsinnige Stelle innerhalb als ausserhalb
10
  sg_buffer[address] = val;
11
}
12
13
char getbuffer(size_t address) {
14
  return sg_buffer[address];
15
}
16
17
// Na gut, jetzt darf doch jeder drauf rumreiten...
18
char * getbuffermem(void) {
19
  return sg_buffer;
20
}
21
22
// Datei b
23
#include "a.h"
24
int main(void) {
25
  char * buf = getbuffermem();
26
  *(buf+10) = 'a';
27
  
28
  char test = getbuffer(10);
29
  disp(test); // 'a'
30
}
Ist dieses Verhalten allgemeingültig oder ist es "Zufall", daß es gerade 
hier funktioniert?

Wird eine globale Variable als "static" deklariert, heißt das zunächst 
einmal nur, daß kein Symbol für den Linker erzeugt wird. Oder können 
noch andere Sachen passieren und das obige Beispiel klappt nur, weil der 
AVR einen flachen Speicherbereich hat? Oder aus noch anderen Gründen?

Viele Grüße
Nicolas

von Klaus W. (mfgkw)


Lesenswert?

Ist ok und portabel.

von Kindergärtner (Gast)


Lesenswert?

Nicolas S. schrieb:
> #define BUFSIZE 1024
Das übliche:
1
static const size_t BUFSIZE = 1024;
ist besser, da scoped & typed.

Einfacher geht das ganze übrigens so:
1
/* die a.h */
2
static const size_t BUFSIZE = 1024;
3
extern char sg_buffer [BUFSIZE];
4
/* die a.c */
5
#include "a.h"
6
char sg_buffer [BUFSIZE];
7
/* Datei b */
8
#include "a.h"
9
int main(void) {
10
  sg_buffer[10] = 'a';
11
  
12
  char test = sg_buffer[10];
13
  disp(test); // 'a'
14
}

Nicolas S. schrieb:
> address %= BUFSIZE;
Das geht nur effizient wenn BUFSIZE eine 2er-Potenz ist, ansonsten 
bekommst du eine Software-Division...

von Nicolas S. (Gast)


Lesenswert?

Kindergärtner schrieb:
> Das geht nur effizient wenn BUFSIZE eine 2er-Potenz ist, ansonsten
> bekommst du eine Software-Division...

Jepp. Aber dann ist es ein Einzeiler (ori).

Kindergärtner schrieb:
> Einfacher geht das ganze übrigens so:

"Einfacher" liegt im Auge des Betrachters. Sinn der Sache ist es ja, den 
Puffer nicht global zu haben (auch wegen des Namensraums), aber in 
Ausnahmefällen den Zugriff trotzdem direkt über Zeiger "ohne Netz und 
doppelten Boden" zu ermöglichen.

Aber bitte wegbleiben vom konkreten Beispiel. "setbuffer()" und 
"getbuffer()" stehen nur als Prototypen für schützende 
Zugriffsfunktionen.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Kindergärtner schrieb:
> Nicolas S. schrieb:
>> #define BUFSIZE 1024
> Das übliche:
> static const size_t BUFSIZE = 1024;
> ist besser, da scoped & typed.

Das geht aber nur in C++.

von Kindergärtner (Gast)


Lesenswert?

Nicolas S. schrieb:
> Jepp. Aber dann ist es ein Einzeiler (ori).
Stimmt...
> "Einfacher" liegt im Auge des Betrachters. Sinn der Sache ist es ja, den
> Puffer nicht global zu haben (auch wegen des Namensraums), aber in
> Ausnahmefällen den Zugriff trotzdem direkt über Zeiger "ohne Netz und
> doppelten Boden" zu ermöglichen.
Dafür sind aber die Funktionen für eben diesen Puffer global, okay, für 
den Range-Check braucht man die.

Yalu X. schrieb:
> Das geht aber nur in C++.
Achja, die Freuden von C.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Yalu X. schrieb:
>> Das übliche:
>> static const size_t BUFSIZE = 1024;
>> ist besser, da scoped & typed.
>
> Das geht aber nur in C++.

Nein, in C auch.  Dort ist es allerdings erstmal implizit eine
Variable (statt einer Konstanten), bei der lediglich eine Veränderung
des Werts durch den Compiler ausgeschlossen wird.

In solch einfachen Fällen wie hier sollte allerdings die Optimierung
die entsprechende Variable hernach eliminieren (da sie "static" ist,
ist für den Compiler ja auch sicher, dass sie außerhalb seiner eigenen
Betrachtung niemand mehr braucht).

von Kindergärtner (Gast)


Lesenswert?


von Klaus W. (mfgkw)


Lesenswert?

1
static const size_t BUFSIZE = 1024;
geht in C natürlich auch, aber nicht dann mit dieser Länge ein Feld zu 
definieren.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Klaus Wachtler schrieb:
> geht in C natürlich auch, aber nicht dann mit dieser Länge ein Feld zu
> definieren.

Ja, so habe ich es oben auch gemeint. Genau dafür wird die Konstante in 
Nicolas Beispiel ja definiert.

von Klaus W. (mfgkw)


Lesenswert?

Sorry, da hatte ich nicht genug zitiert :-(
Ich wollte nicht dir widersprechen bzw. präzisieren, sondern Jörg 
Wunsch:

Jörg Wunsch schrieb:
> Nein, in C auch.

von Nicolas S. (Gast)


Lesenswert?

OK, wenn sich also das Thema Richtung "#define xyz" vs. "static const 
xyz" bewegt schließe ich daraus, daß das Ergebnis der ursrünglichen 
Fragestellung -ob "static"-globale Variablen auch aus anderen Objekten 
heraus problemlos adressierbar sind- unstrittig ist.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Klaus Wachtler schrieb:
> Sorry, da hatte ich nicht genug zitiert :-(
> Ich wollte nicht dir widersprechen bzw. präzisieren, sondern Jörg
> Wunsch:

Das habe ich auch so verstanden. Du hast ja auch geschrieben

> ... aber nicht dann mit dieser Länge ein Feld zu definieren.

Ich glaube, wir sind uns alle bestens einig :)

Nicolas S. schrieb:
> OK, wenn sich also das Thema Richtung "#define xyz" vs. "static const
> xyz" bewegt schließe ich daraus, daß das Ergebnis der ursrünglichen
> Fragestellung -ob "static"-globale Variablen auch aus anderen Objekten
> heraus problemlos adressierbar sind- unstrittig ist.

Ja. Über Pointer kannst du grundsätzlich auf jede lebende Variable 
zugreifen, egal wie eng ihr Sichtbarkeitsbereich ist. Da 
static-Variablen so lange leben wie der gesamte Programmlauf andauert, 
kannst du darauf sogar zu einem beliebigen Zeitpunkt zugreifen.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Klaus Wachtler schrieb:
> geht in C natürlich auch, aber nicht dann mit dieser Länge ein Feld zu
> definieren.

Stimmt, das Detail war mir dabei entgangen.  Danke für die Korrekturen.

Hängt letztlich natürlich damit zusammen, dass es eben keine
Konstante ist.

(Würde in C99 wiederum gehen, wenn das Array innerhalb einer Funktion
ist und die Länge ein Parameter der Funktion, aber das wiederum passt
nicht zum Thema.)

von Kindergärtner (Gast)


Lesenswert?

Jörg Wunsch schrieb:
> (Würde in C99 wiederum gehen, wenn das Array innerhalb einer Funktion
Wobei das wiederum in C++ nicht geht ;) (aus gutem Grund...)

von Klaus W. (mfgkw)


Lesenswert?

Wenn man es in der Funktion wieder static macht, dann schon :-)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Kindergärtner schrieb:
>> (Würde in C99 wiederum gehen, wenn das Array innerhalb einer Funktion
> Wobei das wiederum in C++ nicht geht ;) (aus gutem Grund...)

Was ist denn der „gute Grund“ dafür?

von Kindergärtner (Gast)


Lesenswert?

Jörg Wunsch schrieb:
> Was ist denn der „gute Grund“ dafür?
In kurz, dass es schlecht vorhersehbare (& compilierbare) Dinge mit dem 
Stack anstellt.
In lang: 
https://groups.google.com/d/msg/comp.std.c++/K_4lgA1JYeg/uXqZEYVGycMJ

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Kindergärtner schrieb:
> In kurz, dass es schlecht vorhersehbare (& compilierbare) Dinge mit dem
> Stack anstellt.

Wie jemand im verlinkten Thread schon schrieb: Rekursionen sind da
auch nicht besser, aber keiner kommt auf die Idee, sie deshalb zu
verbieten.

von Kindergärtner (Gast)


Lesenswert?

Jörg Wunsch schrieb:
> Wie jemand im verlinkten Thread schon schrieb
Okay, aber es gibt noch genug weitere Gründe, auch dort diskutiert, muss 
jetzt hier nicht repliziert werden...

von Klaus W. (mfgkw)


Lesenswert?

Jörg Wunsch schrieb:
> Wie jemand im verlinkten Thread schon schrieb: Rekursionen sind da
> auch nicht besser, aber keiner kommt auf die Idee, sie deshalb zu
> verbieten.

Rekursion hat aber viele sinnvolle Anwendungen, auch wenn manche es 
intellektuell nicht raffen.

Bei der Rückgabe einer Adresse einer lokalen (automatischen) Variablen 
muß man aber schon gut nach einer möglichen sinnvollen Anwendung 
suchen...

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Klaus Wachtler schrieb:
> Bei der Rückgabe einer Adresse einer lokalen (automatischen) Variablen
> muß man aber schon gut nach einer möglichen sinnvollen Anwendung
> suchen...

Das man das nicht macht, sollte klar sein. ;-)  Darum ging's aber
auch nicht (auch wenn das jemand in diesem Link als Beispiel
rausgekramt hat).  Solchen Unfug kann man auch ganz ohne VLAs und
auch in C++ anstellen, und ein ordentlicher Compiler sollte das auch
warnen können.

Das man VLAs gedankenlos missbrauchen kann, ist klar.  Aber das trifft
auch für viele andere Dinge zu.

von Kindergärtner (Gast)


Lesenswert?

Jörg Wunsch schrieb:
> Das man VLAs gedankenlos missbrauchen kann, ist klar.
Sie sind jedenfalls ziemlich schwierig in ein Typsystem zu integrieren, 
und daher hat mans lieber gleich bleiben lassen...

von Andreas B. (andreas_b77)


Lesenswert?

Kindergärtner schrieb:
> Jörg Wunsch schrieb:
>> Das man VLAs gedankenlos missbrauchen kann, ist klar.
> Sie sind jedenfalls ziemlich schwierig in ein Typsystem zu integrieren,
> und daher hat mans lieber gleich bleiben lassen...

So allgemein kann man das nicht sagen. Sie sind vielleicht schwierig 
nachträglich ins Typsystem von C/C++ zu integrieren, aber z.B. in Ada 
können Typen indefinit sein (ihre genaue Größe wird also erst durch die 
Initialisierung bestimmt) und da sind VLAs noch ein recht triviales 
Beispiel.

Wobei sich mir jetzt auch nicht erschließt, warum man das gleich ins 
Typsystem integrieren muss, nur um ein paar Arrays variabler Länge 
deklarieren zu können.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Andreas B. schrieb:
> Wobei sich mir jetzt auch nicht erschließt, warum man das gleich ins
> Typsystem integrieren muss, nur um ein paar Arrays variabler Länge
> deklarieren zu können.

Wenn ich die Diskussion da lese: man wollte offenbar einfach bei
C++ das nicht haben.  Da kann man dann tausend Gründe finden …

von Kindergärtner (Gast)


Lesenswert?

Andreas B. schrieb:
> aber z.B. in Ada können Typen indefinit sein
Und in ruby, java, python etc. Aber eine der grundlegenden Grundlagen 
von C,C++ ist, dass Typen eine dem Compiler bekannte fixe Größe haben, 
was jede Menge Probleme generiert, aber einen großen 
Performance-Vorteil bietet. Wenn man das, was man in 
ruby,java,python,ada etc. macht ("indefinite typen") so in C++ machen 
will, nimmt man einfach std::vector und alles ist gut.

Andreas B. schrieb:
> Wobei sich mir jetzt auch nicht erschließt,
Was wäre denn mit:
1
void foo (int n) {
2
  char x [n];
3
  std::cout << sizeof (x); // ja was ist sizeof hier...
4
  static std::array<decltype(x), 7> blub; // und wie groß ist blub?
5
}

von Klaus W. (mfgkw)


Lesenswert?

Kindergärtner schrieb:
> Und in ruby, java, python etc. Aber eine der grundlegenden Grundlagen
> von C,C++ ist, dass Typen eine dem Compiler bekannte fixe Größe haben,

Auf dem Stack gehen in C++ komischerweise trotzdem Felder, deren Größe 
sich erst zur Laufzeit ergibt - mit dem gcc bzw. g++.

> was jede Menge Probleme generiert, aber einen großen
> Performance-Vorteil bietet. Wenn man das, was man in
> ruby,java,python,ada etc. macht ("indefinite typen") so in C++ machen
> will, nimmt man einfach std::vector und alles ist gut.
>
> Andreas B. schrieb:
>> Wobei sich mir jetzt auch nicht erschließt,
> Was wäre denn mit:void foo (int n) {
>   char x [n];
>   std::cout << sizeof (x); // ja was ist sizeof hier...

Halt 5. Was sonst?
Klappt auch, wenn ich den Wert für n beim Aufrufer zur Laufzeit vom 
Benutzer einlese (wieder gcc).

>   static std::array<decltype(x), 7> blub; // und wie groß ist blub?

Geht natürlich nicht, weil Templateparameter dem Compiler bekannt sein 
müssen.

> }

von Yalu X. (yalu) (Moderator)


Lesenswert?

Einiges von dem Folgenden überdeckt sich mit dem, was Klaus schon
geschrieben hat. Ich bin nur nicht dazu gekommen, den Beitrag früher
abzuschicken, und jetzt bin ich zu faul, den Text anzupassen ;-)

Kindergärtner schrieb:
> void foo (int n) {
>   char x [n];
>   std::cout << sizeof (x); // ja was ist sizeof hier...

Das ist kein Problem: sizeof x == n * sizeof (char)

>   static std::array<decltype(x), 7> blub; // und wie groß ist blub?

Hier stört zunächst das static, da nichts Statisches von etwas
abhängig sein kann, was erst zur Laufzeit bekannt wird.

Aber auch ohne static ginge es nicht, da die Template-Parameter zur
Compile-Zeit auswertbar sein müssen.

Das wäre aber auch kein Einschnitt in die C++-Philosophie, da bspw.
1
  std::array<int, n> blub;

für variables n ebenfalls nicht möglich ist, obwohl hier kein VLA
verwendet wird.

Eher etwas unschön (auch in C) sind zweidimensionale VLAs, bei denen
die zweite Dimension variabel ist, bspw.
1
void foo(int n) {
2
  int array[2][n];
3
}

Wird dieses Array nun an eine Funktion
1
void bar(int array[][5]);

übergeben (was zumindest der GCC mit -std=c99 -pedantic akzeptiert),
kann der Compiler nicht mehr wie sonst üblich die Größe der zweiten
Dimension überprüfen was u.U. zur fehlerhaften Indizierung des Arrays in
bar führt. Das wäre so ein Beispiel, wo das ohnehin schon ziemlich
laxe Typsystem von C noch weiter ausgehöhlt wird.

Der C99-Standard hätte auch festlegen können, dass Array-Elemente selbst
keine VLAs sein dürfen, so wie es auch bei Struct- und Union-Elementen
der Fall ist. Dann hätte man dieses Problem nicht. Allerdings sind
gerade mehrdimensionale VLAs mit mehreren variablen Dimensionen in C
äußerst vorteilhaft, weil man diese sonst nur umständlich über
eindimensionale Arrays mit expliziter Indexumrechnung realisieren kann.

von Andreas B. (andreas_b77)


Lesenswert?

Kindergärtner schrieb:
> Andreas B. schrieb:
>> aber z.B. in Ada können Typen indefinit sein
> Und in ruby, java, python etc. Aber eine der grundlegenden Grundlagen
> von C,C++ ist, dass Typen eine dem Compiler bekannte fixe Größe haben,
> was jede Menge Probleme generiert, aber einen großen
> Performance-Vorteil bietet.

Unsinn. Indefinite Typen in Ada kann man nicht als Variable deklarieren 
oder allokieren, weil eben die Größe nicht feststeht. Sie müssen definit 
gemacht werden indem sie explizit oder implizit (durch Initialisierung) 
"constrained" werden. Mit den völlig dynamischen Typen von Ruby, Python 
etc. hat das alles nichts zu tun.

Beispiel: Der vordefinierte Typ String ist ein Array, das so definiert 
ist:
1
type String is array(Positive range <>) of Character;
Das ist indefinit weil für den Wertebereich des Index nur ein 
Platzhalter steht.

Wer eine Variable vom Typ String deklariert, bekommt das vom Compiler 
gleich um die Ohren geknallt. Entweder muss der Indexbereich explizit 
gemacht werden etwa mit String(1 .. 100) für einen 100 Zeichen langen 
String oder es muss gleich mit einer Zeichenfolge initialisiert werden, 
dann wird es genau so lang wie die Initialisierung.

Das Ergebnis ist aber immer ein Array, das nicht anders funktioniert wie 
ein Array in C oder C++, und abzüglich der automatischen Überprüfung der 
Bereichsgrenzen beim Zugriff (falls nicht ohnehin wegoptimiert) genau so 
schnell ist.

> Wenn man das, was man in
> ruby,java,python,ada etc. macht ("indefinite typen") so in C++ machen
> will, nimmt man einfach std::vector und alles ist gut.

Und bei Ada nimmt man Ada.Containers.Vectors und alles ist gut. Und wenn 
man indefinite Typen in den Vector packen will, nimmt man 
Ada.Containers.Indefinite_Vectors und alles ist gut.

von Kindergärtner (Gast)


Lesenswert?

Klaus Wachtler schrieb:
> Auf dem Stack gehen in C++ komischerweise trotzdem Felder, deren Größe
> sich erst zur Laufzeit ergibt - mit dem gcc bzw. g++.
Das ist eine Nonstandard-GCC-Erweiterung...

Yalu X. schrieb:
> Das ist kein Problem: sizeof x == n * sizeof (char)
Das ist ein Problem, denn sizeof muss laut Standard zur Compilezeit 
bekannt sein, so wie "7" oder "4". sizeof(char) ist übrigens immer 1.

Yalu X. schrieb:
> Hier stört zunächst das static, da nichts Statisches von etwas
> abhängig sein kann, was erst zur Laufzeit bekannt wird.
Eher stört das decltype(x), da Typen zur Compilezeit bekannt sein müssen 
- das ist der Sinn eines Typsystems.

Yalu X. schrieb:
> Aber auch ohne static ginge es nicht, da die Template-Parameter zur
> Compile-Zeit auswertbar sein müssen.
Ja, da sizeof() aber zur Compilezeit ausgewertet wird (laut Standard) 
sollte das Ergebnis von sizeof() immer an Templates übergeben werden 
können. Der GCC tut mal wieder Dinge.

Andreas B. schrieb:
> Sie müssen definit
> gemacht werden indem sie explizit oder implizit (durch Initialisierung)
> "constrained" werden.
Ah, interessant. Muss die Größe denn zur Compilezeit bekannt sein oder 
darf sie auch zur Laufzeit berechnet werden? Im letzteren Fall 
involviert das dann die gleichen lustigen Stack-Offset-Berechnungen wie 
bei C99 -Variable Length Arrays (kann man ihre laufzeit-ermittelte Größe 
dann an Templates oder static Variablen übergeben? Kann man sie aus 
Funktionen zurückgeben? => Die schwierige Typsystem-Integration), im 
ersten ist es wie ordinäre fixe arrays wie sie auch im C++ Standard 
sind.

von Andreas B. (andreas_b77)


Lesenswert?

Kindergärtner schrieb:
> Andreas B. schrieb:
>> Sie müssen definit
>> gemacht werden indem sie explizit oder implizit (durch Initialisierung)
>> "constrained" werden.
> Ah, interessant. Muss die Größe denn zur Compilezeit bekannt sein oder
> darf sie auch zur Laufzeit berechnet werden? Im letzteren Fall
> involviert das dann die gleichen lustigen Stack-Offset-Berechnungen wie
> bei C99 -Variable Length Arrays

Ja, auch zur Laufzeit. Ob das jetzt Offset-Berechnungen oder intern 
Pointer auf den Stack ist ja dem Compiler seine Sache. Und die 
Verwendung von Pointern mag kein Vorteil gegenüber dynamisch allokierten 
Variablen sein, aber es spart die Allokation vom Heap und die nötige 
Deallokation.

> (kann man ihre laufzeit-ermittelte Größe
> dann an Templates oder static Variablen übergeben? Kann man sie aus
> Funktionen zurückgeben? => Die schwierige Typsystem-Integration)

Bei Ada heißen Templates Generics und müssen ebenso komplett statisch 
zur Compile-Zeit bestimmt sein, da wird schließlich nach einer Vorlage 
angepasster Code erzeugt.

Man kann sie aber aus Funktionen zurück geben. Das Ergebnis kann man 
keiner Variablen zuweisen, aber man kann Variablen damit initialisieren 
oder den Rückgabewert als Parameter einer anderen Funktion/Prozedur 
verwenden.

Angenommen man hat eine Funktion Get_Line, die eine Zeile von der 
Eingabe liest und als String zurück gibt, dann geht folgende 
Deklaration/Initialisation:
1
Erste_Zeile, Zweite_Zeile : String := Get_Line;
Das ruft Get_Line zwei mal auf und die Variablen bekommen die 
Rückgabewerte von links nach rechts. (Klammern bei Funktionsaufrufen mit 
leerer Parameterliste kann man weglassen.) Und dann sind die konkreten 
Variablen wirklich Strings mit der Länge, die die Rückgabewerte hatten, 
also ist die Länge der ersten Zeile einfach Erste_Zeile'Length mit dem 
Index-Bereich von Erste_Zeile'First bis Erste_Zeile'Last.

Ein Äquivalent zu static Variablen innerhalb Funktionen gibt es nicht, 
das naheliegendste Äquivalent zu globalen Variablen sind solche 
innerhalb von Packages deklarierten. Auch die können mit Rückgabewerten 
von Funktionen initialisiert werden, da kommt dann die Elaboration zum 
Tragen. Wenn die nicht statisch komplett durchgeführt werden kann, wird 
der nötige Rest halt zur Laufzeit vor dem Hauptprogramm durchgeführt.

Wie gesagt existieren auch Vektoren, Maps, Sets etc. in sehr ähnlicher 
Form wie in der C++ STL (ursprünglich wurden sie auch in Ada 
implementiert), aber die Vectors wird man dafür einsetzen, wenn die 
Länge veränderlich ist, etwa weil nach und nach Elemente angehängt 
werden.


Ja, Ada ist relativ komplex zu implementieren. Deshalb gibt es C 
Compiler wie Sand am Meer, aber nur wenige Ada Compiler. Diese 
Komplexität kommt dafür dem Nutzer der Sprache zugute.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Kindergärtner schrieb:
> Yalu X. schrieb:
>> Das ist kein Problem: sizeof x == n * sizeof (char)
> Das ist ein Problem, denn sizeof muss laut Standard zur Compilezeit
> bekannt sein, so wie "7" oder "4".

So war es vor C99, als es noch keine VLAs gab. Mit der Einführung der
VLAs in C99 wurde auch die Definition von sizeof entsprechend
erweitert:

"The sizeof operator yields the size (in bytes) of its operand, which
may be an expression or the parenthesized name of a type. The size is
determined from the type of the operand. The result is an integer. If
the type of the operand is a variable length array type, the operand is
evaluated; otherwise, the operand is not evaluated and the result is an
integer constant."

von Klaus W. (mfgkw)


Lesenswert?

Kindergärtner schrieb:
> Klaus Wachtler schrieb:
>> Auf dem Stack gehen in C++ komischerweise trotzdem Felder, deren Größe
>> sich erst zur Laufzeit ergibt - mit dem gcc bzw. g++.
> Das ist eine Nonstandard-GCC-Erweiterung...
>
> Yalu X. schrieb:
>> Das ist kein Problem: sizeof x == n * sizeof (char)
> Das ist ein Problem, denn sizeof muss laut Standard zur Compilezeit
> bekannt sein, so wie "7" oder "4". sizeof(char) ist übrigens immer 1.

Deswegen schrieb ich ja "mit dem gcc bzw. g++".

Aber danke, daß du es noch zweimal verdeutlicht hast.

Deshalb zur Sicherheit:
Das ist eine gcc-Erweiterung.
Jetzt bist du wieder dran :-)



Was ich damit sagem will, ist zu dem Satz:
Kindergärtner schrieb:
> Aber eine der grundlegenden Grundlagen
> von C,C++ ist, dass Typen eine dem Compiler bekannte fixe Größe haben,
> was jede Menge Probleme generiert,
Die Feldlänge kann mabn im Compiler offenbar flexibler handhaben, wenn 
man will.

von Kindergärtner (Gast)


Lesenswert?

Andreas B. schrieb:
> aber es spart die Allokation vom Heap und die nötige
> Deallokation.
Wie in C99, ja; dafür involviert es besagte Offsetrechnungen, die die 
Optimierung wieder in Frage stellen...

Andreas B. schrieb:
> Das Ergebnis kann man
> keiner Variablen zuweisen
Wieso das den nicht, man muss halt nur die Parameter in der aufrufenden 
Funktion entsprechend mit angeben? In C++ geht das:
1
std::list<int> getList () { return {1, 2, 3}; }
2
int main () {
3
  std::list<int> myList = getList ();
4
}
Oder meinst du etwas anderes?

Andreas B. schrieb:
> Das ruft Get_Line zwei mal auf und die Variablen bekommen die
> Rückgabewerte von links nach rechts.
Okay, praktisch, aber was hat das damit zu tun? :D

Andreas B. schrieb:
> Und dann sind die konkreten
> Variablen wirklich Strings mit der Länge, die die Rückgabewerte hatten
Ja, wie das getline in C++. Der Punkt ist nur, sind die Ergebnis-Strings 
komplett auf dem Stack oder auf dem Heap? In C++ ist es immer letzteres 
(da die Alternative wie gesagt problematisch ist).

Andreas B. schrieb:
> Ja, Ada ist relativ komplex zu implementieren.
C++ auch - Meines Wissens gibt es nur einen einzigen vollständigen C++98 
Compiler ( http://www.comeaucomputing.com/4.0/docs/userman/export.html 
)...

Yalu X. schrieb:
> So war es vor C99, als es noch keine VLAs gab. Mit der Einführung der
> VLAs in C99 wurde auch die Definition von sizeof entsprechend
> erweitert:
Da das aber der C++ Philosophie zuwiderläuft, wollte man es da (in der 
Form) nicht haben :-)

Klaus Wachtler schrieb:
> Jetzt bist du wieder dran :-)
Yey: "Es ist eine GCC-Erweiterung...!" ;-)

Klaus Wachtler schrieb:
> Die Feldlänge kann mabn im Compiler offenbar flexibler handhaben, wenn
> man will.
Scheinbar schon... Aber es ist zumindest "komisch" wenn sizeof() 
plötzlich Laufzeit-Evaluiert ist wenn es sonst immer 
Compilezeit-Evaluiert ist.
Eventuell wird es irgendwann (c++14 ?) VLA's in C++ geben, aber dann 
wahrscheinlich mit einer anderen Syntax als in C, d.h. nicht mit sizeof, 
um dessen Compilezeit-Garantie zu erhalten.

von Kindergärtner (Gast)


Lesenswert?

Andreas B. schrieb:
> Deshalb gibt es C Compiler wie Sand am Meer, aber nur wenige Ada Compiler.
Könnte das nicht eventuell auch daran liegen, dass es etwas mehr C 
Programmierer als Ada Programmierer gibt? :-) Was sich widerum darin 
begründet dass viele Naturwissenschaft/(E-)Technik-Studenten mindestens 
einen C-Crashkurs an der Hochschule erleben dürfen, Ada und C++ sind da 
deutlich seltener (leider)...
Immerhin gibts für jede Plattform, für die es einen GCC gibt, auch 
automatisch einen G++ (C++ Compiler) und einen GNAT (Ada Compiler)...

von Yalu X. (yalu) (Moderator)


Lesenswert?

Kindergärtner schrieb:
> Eventuell wird es irgendwann (c++14 ?) VLA's in C++ geben, aber dann
> wahrscheinlich mit einer anderen Syntax als in C, ...

Mal sehen. In C wurde ja bzgl. der VLAs gerade schon wieder etwas
zurückgerudert. Während sie in C99 noch zwingend vorhanden sein musste,
sind sie in C11 nur noch optional. De Grund dafür liegt aber wohl
weniger darin, dass die VLAs nicht ins Konzept von C passen, sondern
dass sie von Embedded-Entwicklern nur selten benutzt werden. Aus dem
gleichen Grund sind in C11 bspw. auch komplexe Zahlen und Threads
optional.

> ... d.h. nicht mit sizeof, um dessen Compilezeit-Garantie zu erhalten.

Warum muss sizeof eine Compilezeit-Garantie haben? Das ist ein Operator
wie jeder andere auch, und je nach Operand ist das Ergebnis eben
konstant oder auch nicht. Wenn sizeof nicht konstant ist, weil der
Operand ein VLA ist, kann der Ausdruck eben nicht dort stehen, wo ein
konstanter Ausdruck erwartet wird. Das ist aber bei anderen Operationen,
wie bspw. der Addition nicht anders.

von Kindergärtner (Gast)


Lesenswert?

Yalu X. schrieb:
> sondern
> dass sie von Embedded-Entwicklern nur selten benutzt werden
Auf CPU's ohne (effiziente) Stack-Offset-Adressierung wie AVR sind sie 
vermutlich desaströs?

Yalu X. schrieb:
> Das ist ein Operator
> wie jeder andere auch, und je nach Operand ist das Ergebnis eben
> konstant oder auch nicht.
Nein, sizeof() ist (abstrakt) eine Typfunktion, sie erhält einen Typ 
als Argument und liefert eine Konstante (andere Beispiele sind 
std::is_integral oder std::is_same); ein Operator arbeitet auf Werten. 
Das kann man natürlich auch umdefinieren, aber in C++ ist das nunmal so 
gedacht, um das Ergebnis eben an Templates übergeben zu können etc. Für 
ein VLA-Größen-Laufzeit-sizeof() wäre daher zur Vermeidung von 
Verwirrung ein anderer Name sinnvoll - schließlich kann man beide nicht 
austauschbar benutzen. Mal sehen was sich das weise C++ -Kommitee dazu 
überlegt.

Yalu X. schrieb:
> Das ist aber bei anderen Operationen,
> wie bspw. der Addition nicht anders.
An eine Addition kann ich keinen Typ übergeben...

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Kindergärtner schrieb:

> Auf CPU's ohne (effiziente) Stack-Offset-Adressierung wie AVR sind sie
> vermutlich desaströs?

Warum?

Erstens sagt keiner, dass der Datenstack identisch mit dem Returnstack
sein muss.  GCC macht das so, IAR beispielsweise nicht.

Zweitens heißt die Möglichkeit, VLAs zu verwenden, noch lange nicht,
dass man deshalb einen Freibrief bekommt, beliebig große Objekte
damit anzulegen.  Du kannst auch reguläre Arrays auf dem Stack
anlegen, und das theoretische Limit wäre der Index als positive
Zahl vom Typ „int“.  Selbst ein char-Array dieser Größe dürfte die
meisten AVRs deutlich überfordern.

Nur, weil es Programmierer geben könnte, die über die Konsequenzen
ihres Tuns nicht ausreichend nachdenken, muss man deshalb ja nicht
die Sprache so weit amputieren, dass alle anderen Programmierer
dann auch limitiert werden.

> Yalu X. schrieb:
>> Das ist ein Operator
>> wie jeder andere auch, und je nach Operand ist das Ergebnis eben
>> konstant oder auch nicht.

> Nein, sizeof() ist (abstrakt) eine Typfunktion

Nein.  Ich weiß nicht, wie es in C++ ist, aber in C ist sizeof völlig
eindeutig ein Operator.  Du kannst nämlich etwas schreiben wie:
1
   int x[24];
2
3
   ...
4
   for (size_t i = 0; i < sizeof x / sizeof x[0]; i++) {
5
      ...
6
   }

Ohne irgendwelche Klammern nach dem „sizeof“.  Die Klammern sind
nur gefordert, wenn nach dem „sizeof“ ein Typname steht, bei einem
Objekt gehören sie nicht zur Syntax.

Dass die meisten Leute immer Klammern schreiben, liegt vermutlich
daran, dass sie den syntaktischen Unterschied zwischen „sizeof“ eines
Typs und eines Objekts gar nicht kennen, und dass man mit Objekten
halt immer geklammerte Ausdrücke bilden kann.

von Kindergärtner (Gast)


Lesenswert?

Jörg Wunsch schrieb:
> Warum?
Naja, wenn ich erst ein VLA auf dem Stack habe, dann ein paar lokale 
Variablen, dann das nächste VLA, dann die Funktionsargumente, dann muss 
ich zum Laden eines Funktionsargumentes die (dynamischen) Größen der 
VLA's + fixes offset + SP rechnen, also jede Menge 16bit-Operationen auf 
einer 8bit-CPU. Die lokalen Variablen optimiert der Compiler 
(hoffentlich) unter die VLA's, aber die Argumente sind immer drüber...
> Erstens sagt keiner, dass der Datenstack identisch mit dem Returnstack
> sein muss.  GCC macht das so, IAR beispielsweise nicht.
Heißt das auch dass die VLA's grundsätzlich von Funktionsargumenten 
getrennt sind? Muss man dann immer den SP umbiegen für push/pop?

Jörg Wunsch schrieb:
> beliebig große Objekte, damit anzulegen.
Allein die dynamische Größe involviert obige Offset-Berechnungen, 
unabhängig von der tatsächlich auftretenden Größe.

Jörg Wunsch schrieb:
> muss man deshalb ja nicht
> die Sprache so weit amputieren, dass alle anderen Programmierer
> dann auch limitiert werden.
Wenn man das tut erhält man Java :o)

Jörg Wunsch schrieb:
> Ich weiß nicht, wie es in C++ ist, aber in C ist sizeof völlig
> eindeutig ein Operator.
Syntaxmäßig ist es das in C++ auch, aber semantisch erhält sizeof einen 
Typ (wenn man eine Expression übergibt, ob mit oder ohne Klammer, wird 
nur deren Typ genommen, der Wert spielt keine Rolle, da er ja zur 
Compilezeit onehin nicht bekannt ist). Wäre sizeof nicht ein Relikt aus 
C würde es "modern" vermutlich so aussehen: std::sizeof<int>::value, in 
Übereinstimmung mit std::is_integral<int>::value & Friends.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Kindergärtner schrieb:
> Jörg Wunsch schrieb:
>> Warum?
> Naja, wenn ich erst ein VLA auf dem Stack habe, dann ein paar lokale
> Variablen, dann das nächste VLA, dann die Funktionsargumente, dann muss
> ich zum Laden eines Funktionsargumentes die (dynamischen) Größen der
> VLA's + fixes offset + SP rechnen,

Nein.

Erstens werden Funktionsargumente üblicherweise (vorrangig) in
Registern übergeben, und der AVR hat davon in aller Regel genügend.
(Ausnahme: variadic functions, da müssen sie auf dem Stack liegen.)

Zweitens funktioniert das typischerweise, indem man eins der
Zeigerregister als frame pointer benutzt.  Dessen Wert wird zum
Funktionseintritt berechnet, und von da aus werden dann die Offsets
berechnet.

Der Unterschied zwischen IAR und GCC ist dann, dass der IAR das
Y-Register grundsätzlich als Framepointer durchzieht, während der
GCC sich seinen Framepointer beim Funktionseintritt aus dem
Stackpointer errechnet.

> also jede Menge 16bit-Operationen auf
> einer 8bit-CPU.

Naja.  16-bit-Arithmetik beherrscht auch ein AVR, das konnte ja schon
der Z80.

> Die lokalen Variablen optimiert der Compiler
> (hoffentlich) unter die VLA's

Du bist auf dem x86 mit seiner Registerarmut groß geworden, oder? ;-)
Auf einem AVR sind die meisten lokalen Variablen in Registern.

> Jörg Wunsch schrieb:
>> beliebig große Objekte, damit anzulegen.
> Allein die dynamische Größe involviert obige Offset-Berechnungen,
> unabhängig von der tatsächlich auftretenden Größe.

Ja, aber das Problem hat man bei allen dynamischen Angelegenheiten.

Und malloc oder new will im embedded-Bereich ja erst recht keiner
haben, wenn's nicht unbedingt sein muss.

von Kindergärtner (Gast)


Lesenswert?

Jörg Wunsch schrieb:
> Erstens werden Funktionsargumente üblicherweise (vorrangig) in
> Registern übergeben, und der AVR hat davon in aller Regel genügend.
> Zweitens funktioniert das typischerweise, indem man eins der
> Zeigerregister als frame pointer benutzt.
Mh, nagut, ich hatte hier ein bisschen die ARM-Brille auf: Wenige 
Register, Frame Pointer unüblich, dafür effiziente Offset-Adressierung 
(ala "ldr r0, [sp+4]")

Jörg Wunsch schrieb:
> Der Unterschied zwischen IAR und GCC ist dann, dass der IAR das
> Y-Register grundsätzlich als Framepointer durchzieht
Bedeutet dass nicht eher dass es trotzdem nur 1 Stack ist, aber eben mit 
2 Pointern?

Jörg Wunsch schrieb:
> Naja.  16-bit-Arithmetik beherrscht auch ein AVR, das konnte ja schon
> der Z80.
Und jede Turingmaschine... Das Problem ist die Effizienz (bei 
nicht-kompilezeit-bekannten Parametern, also kein "adiw").

Jörg Wunsch schrieb:
> Du bist auf dem x86 mit seiner Registerarmut groß geworden, oder? ;-)
Das ulkige ist ja, der ARM zB hat viel weniger Register als ein AVR, 
aber in Summe trotzdem mehr Bytes ;-)

Jörg Wunsch schrieb:
> Ja, aber das Problem hat man bei allen dynamischen Angelegenheiten.
Aber so viele Angelegenheiten, die Objekte dynamischer Größe auf den 
Stack packen, gibt es nicht; und structs (& Friends) im Heap haben auch 
meist fixe Größen & Offsets.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Kindergärtner schrieb:

>> Der Unterschied zwischen IAR und GCC ist dann, dass der IAR das
>> Y-Register grundsätzlich als Framepointer durchzieht
> Bedeutet dass nicht eher dass es trotzdem nur 1 Stack ist, aber eben mit
> 2 Pointern?

Nein, der IAR hat zwei Stacks.  Macht zwar den Framepointer einfacher,
aber dafür muss man (mindestens) einen der beiden halt hart zur
Compilezeit in der Größe festlegen.

> Und jede Turingmaschine... Das Problem ist die Effizienz (bei
> nicht-kompilezeit-bekannten Parametern, also kein "adiw").

Ist so tragisch auch nicht.

Wie geschrieben: jede andere Lösung eines vergleichbaren dynamischen
Problems wäre ja nicht einfacher.

Wenn man etwas statisch machen kann, gibt es keinen Grund für VLAs.

>> Du bist auf dem x86 mit seiner Registerarmut groß geworden, oder? ;-)

> Das ulkige ist ja, der ARM zB hat viel weniger Register als ein AVR,
> aber in Summe trotzdem mehr Bytes ;-)

Ja, aber dadurch, dass sie eben selbst für ein Byte ein Register
benutzen müssen, geht dann doch mehr über den Speicher.

Ohne die vielen Register wäre der AVR vermutlich auch nicht das
geworden, was er geworden ist.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Jörg Wunsch schrieb:
> Kindergärtner schrieb:
>> Jörg Wunsch schrieb:
>>> Warum?
>> Naja, wenn ich erst ein VLA auf dem Stack habe, dann ein paar lokale
>> Variablen, dann das nächste VLA, dann die Funktionsargumente, dann muss
>> ich zum Laden eines Funktionsargumentes die (dynamischen) Größen der
>> VLA's + fixes offset + SP rechnen,
>
> Nein.
>
> Erstens werden Funktionsargumente üblicherweise (vorrangig) in
> Registern übergeben, und der AVR hat davon in aller Regel genügend.
> (Ausnahme: variadic functions, da müssen sie auf dem Stack liegen.)

Ich habe mal nachgeschaut, wie die Stack-Organisation beim AVR-GCC ist:

Alle in einer Funktion verwendeten VLAs liegen unterhalb des Frames für 
lokale Variablen. Auf normale Variablen kann damit ohne 
Effizienznachteile genauso zugegriffen werden wie beim Nichtvorhandesein 
von VLAs.

Der Zugriff auf die VLAs ist logischerweise etwas aufwendiger, da deren 
Startadressen jeweils zur Laufzeit berechnet werden müssen. Sind 
genügend Register vorhanden, geschieht diese Berechnung aber pro 
Funktionsaufruf nur einmal. Die berechnete Startadresse des erste VLAs 
wird im Z-Registerpaar abgelegt, so dass auf die einzelnen Elemente mit 
LD/ST Z+n ebenfalls sehr schnell zugegriffen werden kann. Weiteren 
VLA-Startadressen gelangen in andere Registerpaare und werden für 
VLS-Zugriffe temporär ins X-Registerpaar kopiert.

PS: Auch wenn das Ursprungsthema zur Zufriedenheit des TE abgeschlossen 
ist: Sollten wir für die VLAs nicht vielleicht einen neuen Thread 
beginnen?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Yalu X. schrieb:
> Sollten wir für die VLAs nicht vielleicht einen neuen Thread beginnen?

Naja, ich denke, dass mittlerweile alles so einigermaßen geschrieben
worden ist.

Danke für deine Analyse!

von Nicolas S. (Gast)


Lesenswert?

Och, ich lese immer noch und staune :-)

von Kindergärtner (Gast)


Lesenswert?

Ich sehe schon, wehe man sagt hier was gegen AVR ;-)

Jörg Wunsch schrieb:
> Nein, der IAR hat zwei Stacks.
Hm, macht so eine Konstruktion auch Sinn wenn jedes Register als 
Adresszeiger benutzt werden kann (wie ARM)?

Jörg Wunsch schrieb:
> Ja, aber dadurch, dass sie eben selbst für ein Byte ein Register
> benutzen müssen, geht dann doch mehr über den Speicher.
Jo, dafür ist das da auch schneller. Man kann zwar leicht mehrere Bytes 
in ein 32bit-Register packen (und wieder entpacken), nur dann nicht mit 
einzelnen Bytes rechnen...

Jörg Wunsch schrieb:
> Ohne die vielen Register wäre der AVR vermutlich auch nicht das
> geworden, was er geworden ist.
Wobei man 16 davon nicht bei allen Instruktionen verwenden kann...

Yalu X. schrieb:
> Alle in einer Funktion verwendeten VLAs liegen unterhalb des Frames
Hm, und was passiert (rein akademisch betrachtet) wenn man mehr als 
32bytes an Funktionsargumenten übergibt?

Nicolas S. schrieb:
> Och, ich lese immer noch und staune :-)
Heh... Sozialstudien über Internetdiskussionen?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Kindergärtner schrieb:
> Ich sehe schon, wehe man sagt hier was gegen AVR ;-)

Damit hat das doch nicht viel zu tun.  Ist ja nur, dass du am
Beispiel des AVR die vorgebliche Unpraktikabilität von VLAs
nachweisen wolltest. ;-)

>> Nein, der IAR hat zwei Stacks.
> Hm, macht so eine Konstruktion auch Sinn wenn jedes Register als
> Adresszeiger benutzt werden kann (wie ARM)?

Eigentlich hat die Konstruktion nichtmal beim AVR wirklich Sinn.
Die Leute von IAR kennen das aber offenbar nur so, und da sie
konsultativ beim Design des AVR mit dabei waren, gab es eben
niemanden, der damals auch darauf geachtet hätte, dass man
SP-relativ sinnvoll adressieren kann.  Dadurch hat es der GCC (der
in der Unix-Tradition alles in einem Stack macht) etwas schwerer.

(Wenn mich nicht alles täuscht, dürfte der single stack beim ARM
dagegen durch das ABI vorgegeben sein, da kommt dann also auch IAR
nicht umhin.)

>> Ohne die vielen Register wäre der AVR vermutlich auch nicht das
>> geworden, was er geworden ist.

> Wobei man 16 davon nicht bei allen Instruktionen verwenden kann...

Sind halt trotzdem gut für lokale Variablen oder Funktionsparameter,
da der Zugriff schneller als SRAM ist.  Viele Funktionen kommen beim
AVR daher ohne Variablen im Stack aus (der wird dann nur zum Pushen der
callee saved registers gebraucht).

> Hm, und was passiert (rein akademisch betrachtet) wenn man mehr als
> 32bytes an Funktionsargumenten übergibt?

Irgendwann muss halt mal der Stack zu Hilfe genommen werden, passiert
aber in der Praxis (bis auf variadic functions) eigentlich nie.

von Kindergärtner (Gast)


Lesenswert?

Jörg Wunsch schrieb:
> Ist ja nur, dass du am
> Beispiel des AVR die vorgebliche Unpraktikabilität von VLAs
> nachweisen wolltest. ;-)
Nö, das war nur eine Überlegung dass das beim AVR eventuell schwierig 
sein könnte, und weit entfernt von einem Beweisversuch...

Jörg Wunsch schrieb:
> Eigentlich hat die Konstruktion nichtmal beim AVR wirklich Sinn.
Oh gut.

Jörg Wunsch schrieb:
> dass man SP-relativ sinnvoll adressieren kann.
das ist vermutlich nicht so ganz einfach in die Architektur zu 
integrieren, wenn es schneller als adiw+ld/st sein soll...

Jörg Wunsch schrieb:
> Wenn mich nicht alles täuscht, dürfte der single stack beim ARM, dagegen durch 
das ABI vorgegeben sein
Das AVR-GCC-ABI gibt auch einen Single-Stack vor :-) ... Aber an ABI's 
muss sich ja keiner halten, und ich glaube sogar dass der SP beim ARM 
einfach nur ein normales Register ist, und man eigentlich pro Register 
einen Stack haben könnte. Da ARM (Thumb) ja auch kein "CALL" sondern nur 
"B"ranch hat, und man die Rücksprungadresse, falls nötig, explizit 
zusammen mit den Caller-Registern effizient pusht, und das 
Basis-Register dafür ebenfalls frei wählbar ist, bräuchte man nicht 
einmal einen Stackpointer austauschen, sondern kann quasi bei jeder 
Nutzung angeben welchen Stack man haben möchte. Ohne Garantie, kann ich 
gerade nicht nachschauen.

Jörg Wunsch schrieb:
> Viele Funktionen kommen beim AVR daher ohne Variablen im Stack aus
ja, das rettet es wohl :)

Jörg Wunsch schrieb:
> bis auf variadic functions
Und die sind ja eh böse duck

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Kindergärtner schrieb:

>> dass man SP-relativ sinnvoll adressieren kann.
> das ist vermutlich nicht so ganz einfach in die Architektur zu
> integrieren, wenn es schneller als adiw+ld/st sein soll...

Doch, es gibt ja auch Adressierung relativ zu den drei
Zeigerregisterpaaren.  Den SP hätte man genauso da mit integrieren
können, wenn denn damals nur jemand soweit gedacht hätte.

Gut, für VLAs muss man zu Fuß rechnen, denn die relative Adressierung
funktioniert so wie seinerzeit auch beim Z80, der Offset also in den
Opcode eingebaut.  Würde aber für normale Stackadressierung trotzdem
helfen.

> Das AVR-GCC-ABI gibt auch einen Single-Stack vor :-) ... Aber an ABI's
> muss sich ja keiner halten, ...

Naja, wenn der Prozessorhersteller schon eins vorgibt, tut man gut
daran, das nicht komplett zu ignorieren.  GCC hat ja auch den Schwenk
zum offiziellen ARM-ABI gemacht.

von Kindergärtner (Gast)


Lesenswert?

Jörg Wunsch schrieb:
> Naja, wenn der Prozessorhersteller schon eins vorgibt, tut man gut
> daran, das nicht komplett zu ignorieren.
Aber so kriegt man keine Multistacks!11 Solange man keine binären 
Libraries einbindet... ;-)

Jörg Wunsch schrieb:
> Den SP hätte man genauso da mit integrieren
> können, wenn denn damals nur jemand soweit gedacht hätte.
Aus irgendwelchen obskuren Gründen ist der ja auch ein I/O-Register...

von Nicolas S. (Gast)


Lesenswert?

Kindergärtner schrieb:
> Heh... Sozialstudien über Internetdiskussionen?

Nö. Auch wenn das ebenfalls interessant ist, daß ab einer gewissen 
Komplexität des Themas die Anzahl der Trollversuche sehr klein wird.

Für mich sieht das im Moment nach einer von jenen absolut theoretischen 
Diskussionen aus, deren Inhalt ich im Moment nur sehr oberflächlich 
folgen kann und die für meinen Alltag momentan keinerlei Relevanz haben, 
da sie meine eigenen Interessen nicht berühren. Oft ist es mir 
allerdings schon passiert, daß so aufgenommene Wissensbrocken Jahre 
später urplötzlich sehr interessant wurden - und dann entweder ein 
konkretes Problem gelöst oder einfach einen neuen Blickwinkel erlaubt 
haben.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Kindergärtner schrieb:
> Yalu X. schrieb:
>> Alle in einer Funktion verwendeten VLAs liegen unterhalb des Frames
> Hm, und was passiert (rein akademisch betrachtet) wenn man mehr als
> 32bytes an Funktionsargumenten übergibt?

Nichts, was im Zusammenhang mit den VLAs steht.

Wenn sämtliche Argumente und automatischen Variablen in Registern 
gehalten werden können (leider stehen dafür deutlich weniger als 32 zur 
Verfügung), muss der Frame gar nicht erst erstellt werden.

Reichen die Register nicht aus, werden Strukturen als Argumente oder 
automatische Variablen verwendet oder ist die Funktion variadic, wird 
ein Frame angelegt. Das ist auf dem AVR im Gegensatz zu anderen 
Prozessoren eine relativ aufwendige Angelegenheit, weil er keine 
geeigneten Maschinenbefehle dafür hat. Anschließend kann auf alle 
Argumente und Variablen auf dem Stack per frame-pointer-relativer 
Adressierung (beim AVR ld/st Y+n) zugegriffen werden.

Das ist ohne VLAs so und mit VLAs ebenso.

von Kindergärtner (Gast)


Lesenswert?

Yalu X. schrieb:
> Das ist ohne VLAs so und mit VLAs ebenso.
Ja, nur wenn sich die fraglichen Objekte im Frame, wie eben 
Funktionsargumente, über (d.h. größere Adresse) dem VLA befinden, wie 
kommt man dann dran? Indem man einen Pointer darauf in Y-Register 
behält?

Yalu X. schrieb:
> werden Strukturen als Argumente
Nanu, seit wann müssen Strukturen grundsätzlich im RAM sein? Die kann 
der Compiler doch auch in ihre einzelnen Felder aufteilen und die per 
Register übergeben.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Kindergärtner schrieb:
> Ja, nur wenn sich die fraglichen Objekte im Frame, wie eben
> Funktionsargumente, über (d.h. größere Adresse) dem VLA befinden, wie
> kommt man dann dran? Indem man einen Pointer darauf in Y-Register
> behält?

Ja, genau. Und das nennt sich eben Frame-Pointer.

> Nanu, seit wann müssen Strukturen grundsätzlich im RAM sein? Die kann
> der Compiler doch auch in ihre einzelnen Felder aufteilen und die per
> Register übergeben.

Klar könnten – zumindest kleine – Strukturen auch per Register übergeben 
werden. Zumindest der GCC scheint dies aber nicht zu tun, nicht einmal 
bei 1-Byte-Strukturen. Vielleicht hängt das damit zusammen, dass eine 
diesbezügliche Unterscheidung zwischen kleinen und großen Strukturen das 
ABI verkomplizieren würde.

von Klaus W. (mfgkw)


Lesenswert?

Für Strukturen ist m.W. ja auch garantiert, daß sie in der Reihenfolge 
ihrer Elemente im Speicher liegen müssen (wenn auch nicht lückenlos).
D.h. in der Funktion müsste dann aus den Registerwerten wieder die 
struct zusammengebastelt werden, was dann natürlich jede Beschleunigung 
verhindert.

(ok, diese Einschränkung wäre nicht nötig, wenn der Compiler sicher 
sieht, daß kein Schmu mit den Elementen getrieben wird)

von Kindergärtner (Gast)


Lesenswert?

Yalu X. schrieb:
> Zumindest der GCC scheint dies aber nicht zu tun, nicht einmal
> bei 1-Byte-Strukturen.
Interessant, ich habe das mal ausprobiert:
Auf ARM EABI Thumb2 und AVR übergibt er structs per Register (auch ohne 
Optimierungen) - genau wie er die einzelnen Werte übergeben würde.
Auf x86(_64) übergibt er einzelne Variablen als Register wenn man die 
"fastcall"-Calling-Convention verwendet, aber structs immer per Stack.

Letzteres ist etwas beunruhigend, denn für C++ verlässt man sich zwecks 
Abstraktion ziemlich darauf dass das umherreichen kleiner 
structs/classes effizient (d.h. genauso schnell wie für die einzelnen 
Felder) ist...

Yalu X. schrieb:
> Vielleicht hängt das damit zusammen, dass eine
> diesbezügliche Unterscheidung zwischen kleinen und großen Strukturen das
> ABI verkomplizieren würde.
Zumindest für AVR und ARM EABI scheint man die Argumente einfach als 
"flache" Folge von "CPU-Worten" (AVR: Byte, ARM: 32bit-Word) zu 
betrachten (unabhängig davon welches der Worte ein "int" oder Teil eines 
struct ist), und den Anfang der Folge per Register und den Rest per 
Stack zu übergeben. Mit anderen Worten, der Stack wird "nach unten" in 
die Register erweitert, völlig egal wie die Struktur der Werte im C-Code 
aussieht.

Klaus Wachtler schrieb:
> D.h. in der Funktion müsste dann aus den Registerwerten wieder die
> struct zusammengebastelt werden, was dann natürlich jede Beschleunigung
> verhindert.
Wieso das? Solange man in der Funktion keinen Pointer auf die Felder im 
struct anlegt, kann man die Werte ja ganz gewöhnlich "direkt" für 
Rechnungen verwenden, d.h. sie können wunderbar in Registern bleiben 
(sofern ausreichend vorhanden). Erst wenn man den struct memcpy'en will 
oder einen Pointer darauf herumreichen, muss er in den adressierbaren 
Speicher, d.h. den Stack. Das ganze funktioniert so ganz wunderbar in 
meinem ARM-Projekt...

von Andreas B. (andreas_b77)


Lesenswert?

Kindergärtner schrieb:
> Andreas B. schrieb:
>> Das Ergebnis kann man
>> keiner Variablen zuweisen
> Wieso das den nicht, man muss halt nur die Parameter in der aufrufenden
> Funktion entsprechend mit angeben? In C++ geht das:std::list<int>
> getList () { return {1, 2, 3}; }
> int main () {
>   std::list<int> myList = getList ();
> }Oder meinst du etwas anderes?

Das ist eine Initialisierung, das ginge natürlich. Mit Zuweisung meine 
ich Zuweisung im engen Sinne, also einer existierende Variable einen 
neuen Wert zu geben. Eine Variable kann nicht im Nachhinein ihre 
Speichergröße ändern, deshalb geht das nicht und das meinte ich.

> Andreas B. schrieb:
>> Das ruft Get_Line zwei mal auf und die Variablen bekommen die
>> Rückgabewerte von links nach rechts.
> Okay, praktisch, aber was hat das damit zu tun? :D

Das war nur nebenbei, der eigentlich Punkt war, dass die Variablen dann 
die tatsächliche Größe der Strings haben.

> Andreas B. schrieb:
>> Und dann sind die konkreten
>> Variablen wirklich Strings mit der Länge, die die Rückgabewerte hatten
> Ja, wie das getline in C++. Der Punkt ist nur, sind die Ergebnis-Strings
> komplett auf dem Stack oder auf dem Heap? In C++ ist es immer letzteres
> (da die Alternative wie gesagt problematisch ist).

Nun gut, das ist wohl implementation defined. GNAT (gcc) verwendet für 
Rückgabewerte mit variable Größe einen Zweitstack, für den es dann auch 
mehr Speicher allokieren muss sobald nötig. Aber es wird nicht für jedes 
Objekt separat Speicher allokiert. Tatsächlich gibt es laut GNAT 
Reference Manual ausschließlich diese Fälle, in denen implizit vom Heap 
allokiert wird:

* At initial elaboration time, to allocate dynamically sized global 
objects.
* To allocate space for a task when a task is created.
* To extend the secondary stack dynamically when needed. The secondary 
stack is used for returning variable length results.


Kindergärtner schrieb:
> Andreas B. schrieb:
>> Deshalb gibt es C Compiler wie Sand am Meer, aber nur wenige Ada Compiler.
> Könnte das nicht eventuell auch daran liegen, dass es etwas mehr C
> Programmierer als Ada Programmierer gibt? :-)

Sicher, aber ich meinte auch die relativen Zahlen. Selbst wenn das 
Interesse an Ada gleich groß wäre würde es wahrscheinlich immer noch 
mehr C Compiler geben weil die Sprache (Runtime und Libraries gar nicht 
eingerechnet) einfacher zu implementieren ist.

> Immerhin gibts für jede Plattform, für die es einen GCC gibt, auch
> automatisch einen G++ (C++ Compiler) und einen GNAT (Ada Compiler)...

So einfach ist es dann auch nicht, denn Ada hat auch viele Features die 
implizit in die Runtime springen und systemspezifisch sind. Auf den 
großen Betriebssystemen kommt man mit dem GNAT gut zurecht, auf embedded 
Systemen wird die Auswahl ohne viel Geld hinzublättern (AdaCore hat 
einiges im Angebot) sehr überschaubar.

RTEMS hat volle Ada Unterstützung, ganz ohne Betriebssystem wirds eng. 
Es gibt die Projekte avr-ada und msp430ada. avr-ada hat gar keine 
Runtime und ist in vielen Belangen eingeschränkt, z.B. unterstützt es 
nicht die Standard-Methode um Handler an Interrupts zu koppeln: 
http://sourceforge.net/p/avr-ada/wiki/Interrupts/

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Kindergärtner schrieb:

> Auf ARM EABI Thumb2 und AVR übergibt er structs per Register (auch ohne
> Optimierungen)

Klar, das ABI muß natürlich unabhängig von der Optimierungsstufe sein!

...ebenso wie z.B. die C-Semantik.

von Kindergärtner (Gast)


Lesenswert?

Andreas B. schrieb:
> GNAT (gcc) verwendet für
> Rückgabewerte mit variable Größe einen Zweitstack, für den es dann auch
> mehr Speicher allokieren muss sobald nötig.
Das ist interessant, C, C++ kann das wohl überhaupt nicht.

Andreas B. schrieb:
> in denen implizit vom Heap
> allokiert wird:
Aber sowas wie klassische verkettete Listen gehen trotzdem, die 
(explizit) auf dem Heap dynamisch allokieren?

Andreas B. schrieb:
> So einfach ist es dann auch nicht, denn Ada hat auch viele Features die
> implizit in die Runtime springen und systemspezifisch sind. Auf den
> großen Betriebssystemen kommt man mit dem GNAT gut zurecht,
Hm, ist das nicht ein Widerspruch, dynamische Allokation zu vermeiden 
und alles auf den Stack zu packen um ein paar Zyklen zu sparen, aber 
dann doch aufwändige Betriebssysteme zu erfordern?

Andreas B. schrieb:
> z.B. unterstützt es
> nicht die Standard-Methode um Handler an Interrupts zu koppeln:
Ada hat eine Standard-Methode zur Interruptbehandlung, braucht aber ein 
betriebssystem? Wo kann man das denn einsetzen...

Johann L. schrieb:
> Klar, das ABI muß natürlich unabhängig von der Optimierungsstufe sein!
>
> ...ebenso wie z.B. die C-Semantik.
Jo, klar, ich wollte nur ganz sicher gehen ;)

von Andreas B. (andreas_b77)


Lesenswert?

Kindergärtner schrieb:
> Andreas B. schrieb:
>> GNAT (gcc) verwendet für
>> Rückgabewerte mit variable Größe einen Zweitstack, für den es dann auch
>> mehr Speicher allokieren muss sobald nötig.
> Das ist interessant, C, C++ kann das wohl überhaupt nicht.

Brauchen sie auch nicht, da kann man ja allein von der Sprachdefinition 
her keine Objekte variabler Länge aus einer Funktion zurückgeben.

> Andreas B. schrieb:
>> in denen implizit vom Heap
>> allokiert wird:
> Aber sowas wie klassische verkettete Listen gehen trotzdem, die
> (explizit) auf dem Heap dynamisch allokieren?

Zeiger (in Ada heißen sind das die "access" Typen) und Heap-Allokation 
gibt es selbstverständlich. Aber halt viel sicherer als in C, solange 
man die Sicherheitsmechanismen nicht explizit umgeht.

> Andreas B. schrieb:
>> So einfach ist es dann auch nicht, denn Ada hat auch viele Features die
>> implizit in die Runtime springen und systemspezifisch sind. Auf den
>> großen Betriebssystemen kommt man mit dem GNAT gut zurecht,
> Hm, ist das nicht ein Widerspruch, dynamische Allokation zu vermeiden
> und alles auf den Stack zu packen um ein paar Zyklen zu sparen, aber
> dann doch aufwändige Betriebssysteme zu erfordern?

Ada erfordert kein Betriebssystem, die Runtime muss halt die nötigen 
Features implementieren. Nur ist bei dem einzigen Open Source GNAT / gcc 
die Standard Open Source Runtime für Linux / Unix ausgelegt. Wie gesagt, 
AdaCore hat auch Lösungen für Embedded im Angebot, inklusive Bare Metal 
ohne Betriebssystem: http://www.adacore.com/gnatpro/embedded/ . Aber 
eben nur für (wahrscheinlich viel) Geld.

> Andreas B. schrieb:
>> z.B. unterstützt es
>> nicht die Standard-Methode um Handler an Interrupts zu koppeln:
> Ada hat eine Standard-Methode zur Interruptbehandlung, braucht aber ein
> betriebssystem? Wo kann man das denn einsetzen...

Wie gesagt, es braucht kein Betriebssystem solange man eine passende 
Runtime hat. Tatsächlich werden in der Standard-Runtime von GNAT die 
Prozess-Signale als Interrupts verwendet.

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.