Forum: Compiler & IDEs GCC Statische initialisierung.


von Hans-Georg L. (h-g-l)


Lesenswert?

Bei einem Placement new oder std::construct_at sollte doch der Compiler 
einfach die Adresse zuweisen und keine statische Initialisierung machen. 
Wenn ich den nachfolgenden Code im Compiler Explorer 
https://gcc.godbolt.org/ eingebe zeigt mir der GCC 12.2.noeabi bei 
beiden Varianten statischen Initialisierungscode an. Warum ?
Optionen : -std=c++20 -Og
1
#include <cstdint>
2
#include <memory> 
3
4
const std::uintptr_t CONST_GPIOA_BASE = 0x40020000UL;
5
const std::uintptr_t CONST_GPIOB_BASE = 0x40020400UL;
6
7
struct gpio_type {  
8
    volatile std::uint32_t moder;    // offset: 0x00
9
    volatile std::uint32_t otyper;   // offset: 0x04
10
    volatile std::uint32_t ospeedr;  // offset: 0x08
11
    volatile std::uint32_t pupdr;    // offset: 0x0C    
12
    volatile std::uint32_t idr;      // offset: 0x10 
13
    volatile std::uint32_t odr;      // offset: 0x14  
14
    volatile std::uint32_t bssr;     // offset: 0x18  
15
    volatile std::uint32_t lckr;     // offset: 0x1C 
16
    volatile std::uint32_t afr[2];   // offset: 0x20/0x24  
17
};
18
19
// placement new 
20
auto gpio_A = new (reinterpret_cast<void*>(CONST_GPIOA_BASE)) gpio_type;
21
22
// std::construct_at (C++20)
23
auto gpio_B = std::construct_at(reinterpret_cast<gpio_type*>CONST_GPIOA_BASE));
24
25
int main()
26
{
27
    gpio_A->moder = 4711;
28
    gpio_B->moder = 4712;
29
}
>

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Hans-Georg L. schrieb:
> Warum ?

Weil deine Variablen nicht constinit/constexpr sind und daher eben 
Laufzeit-initialisiert werden. Eigentlich müsstest du "constinit" 
hinzufügen, aber reinterpret_cast ist in constant expressions nicht 
erlaubt.

placement-new bzw construct_at ist eigentlich eh das falsche Werkzeug, 
du willst ja keinen Konstruktor auf den Peripherie-Registern aufrufen!

Man muss den Cast auf die Laufzeit verschieben, und im constant 
expression Kontext halt nur mit dem Integer hantieren. z.B. so:
1
#include <cstdint>
2
3
#include <memory> 
4
5
constexpr std::uintptr_t CONST_GPIOA_BASE = 0x40020000UL;
6
7
constexpr std::uintptr_t CONST_GPIOB_BASE = 0x40020400UL;
8
9
struct gpio_type {  
10
11
    volatile std::uint32_t moder;    // offset: 0x00
12
13
    volatile std::uint32_t otyper;   // offset: 0x04
14
15
    volatile std::uint32_t ospeedr;  // offset: 0x08
16
17
    volatile std::uint32_t pupdr;    // offset: 0x0C    
18
19
    volatile std::uint32_t idr;      // offset: 0x10 
20
21
    volatile std::uint32_t odr;      // offset: 0x14  
22
23
    volatile std::uint32_t bssr;     // offset: 0x18  
24
25
    volatile std::uint32_t lckr;     // offset: 0x1C 
26
27
    volatile std::uint32_t afr[2];   // offset: 0x20/0x24  
28
29
};
30
31
template <typename PeriphType, std::uintptr_t Addr>
32
struct PeriphWrapper {
33
    [[gnu::always_inline]] inline PeriphType& get () const { return *reinterpret_cast<PeriphType*> (Addr); }
34
    [[gnu::always_inline]] inline PeriphType* operator -> () const { return &get (); }
35
};
36
37
static constexpr PeriphWrapper<gpio_type, CONST_GPIOA_BASE> gpio_A;
38
static constexpr PeriphWrapper<gpio_type, CONST_GPIOB_BASE> gpio_B;
39
40
int main()
41
{
42
43
    gpio_A->moder = 4711;
44
    gpio_B->moder = 0x815;
45
46
}

https://gcc.godbolt.org/z/cejd945na

Die Variablen gpio_A und gpio_B sind leere Dummys und belegen keinen 
Speicher, erst beim Aufruf des Pfeil-Operators erfolgt der Cast. Also 
eben zur Laufzeit, nur durch das Inlinen wird es dann effizient.

Aber ob das jetzt so schön ist...

: Bearbeitet durch User
von Richard W. (richardw)


Lesenswert?

Niklas G. schrieb:
> Aber ob das jetzt so schön ist...

Also ich würd's kaufen. Insbesondere die Trennung der Registerstruktur 
und der Klasse für den Speicherzugriff ist doch sehr schön. So ginge 
auch:
1
#include <cstdint>
2
3
constexpr std::uintptr_t CONST_GPIOA_BASE = 0x40020000UL;
4
constexpr std::uintptr_t CONST_GPIOB_BASE = 0x40020400UL;
5
6
template<std::uintptr_t Addr>
7
struct gpio_type {
8
    std::uint32_t moder;    // offset: 0x00
9
    std::uint32_t otyper;   // offset: 0x04
10
    std::uint32_t ospeedr;  // offset: 0x08
11
    std::uint32_t pupdr;    // offset: 0x0C
12
    std::uint32_t idr;      // offset: 0x10
13
    std::uint32_t odr;      // offset: 0x14
14
    std::uint32_t bssr;     // offset: 0x18
15
    std::uint32_t lckr;     // offset: 0x1C
16
    std::uint32_t afr[2];   // offset: 0x20/0x24
17
18
    inline volatile gpio_type* operator -> () const { return reinterpret_cast<gpio_type*>(Addr); }
19
};
20
21
using GPIOA = gpio_type<CONST_GPIOA_BASE>;
22
using GPIOB = gpio_type<CONST_GPIOB_BASE>;
23
24
void test()
25
{
26
    GPIOA()->moder = 0x4711;
27
    GPIOB()->moder = 0x815;
28
}

https://gcc.godbolt.org/z/E94PfaPrK

Sieht auf den ersten Blick ein klitzeklein wenig einfacher aus weil auf 
die Trennung verzichtet wird, ebenso auf die statische Variable. Es wird 
trotzdem gleiche Maschinencode erzeugt. Mit -Os oder -Oz wird's sogar 
noch kleiner.

: Bearbeitet durch User
von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Richard W. schrieb:
> Sieht auf den ersten Blick ein klitzeklein wenig einfacher aus

Gefällt mir nicht so ganz weil es so keinen nicht-templatisierten 
"gpio_type" gibt. Dadurch kann man kein "gpio_type*" als 
Funktionsparameter übergeben o.ä.

Beitrag #7938480 wurde vom Autor gelöscht.
von Veit D. (devil-elec)


Lesenswert?

Hallo,

mein Grundgerüst aus 2021 sieht wie folgt aus. Ist erstmal viel 
Tipparbeit aus dem Manual um die Registerstruktur aufzubauen. Ab da geht 
es leichter.
1
AVRxDB32_PinRegister.h
2
3
#pragma once
4
5
namespace Pins
6
{
7
  namespace Addr
8
  {
9
    struct Offset // Offsets der Registeradressen
10
    {
11
      // VPORTs
12
      const uint8_t vDir  = 0x00;
13
      const uint8_t vOut  = 0x01;
14
      const uint8_t vIn   = 0x02;
15
      const uint8_t vFlag = 0x03;
16
      // PORTs
17
      const uint8_t dir           = 0x00;
18
      const uint8_t dirSet        = 0x01;
19
      const uint8_t dirClear      = 0x02;
20
      const uint8_t dirToggle     = 0x03;
21
      const uint8_t out           = 0x04;
22
      usw.
23
    };  
24
    constexpr Offset addrOffset;
25
26
    struct Address // base addresses of registers
27
    {
28
      uint16_t vport;  
29
      uint16_t port;
30
      uint8_t  pinCtrl;
31
      uint8_t  mask;
32
    };
33
34
    constexpr Address baseAddr[]
35
    {
36
    // | VPORT | PORT | PINn | BIT  | //     | Arduino | Package |
37
    // | Base  | Base | CTRL | MASK | // BIT |   Pin   |   Pin   | PORT
38
       {0x0000, 0x0400, 0x10, 0x01},  //  0  |     0   |    30   | PA0
39
       {0x0000, 0x0400, 0x11, 0x02},  //  1  |     1   |    31   | PA1
40
       {0x0000, 0x0400, 0x12, 0x04},  //  2  |     2   |    32   | PA2       
41
       {0x0000, 0x0400, 0x13, 0x08},  //  3  |     3   |     1   | PA3
42
       {0x0000, 0x0400, 0x14, 0x10},  //  4  |     4   |     2   | PA4
43
       {0x0000, 0x0400, 0x15, 0x20},  //  5  |     5   |     3   | PA5
44
       {0x0000, 0x0400, 0x16, 0x40},  //  6  |     6   |     4   | PA6
45
       {0x0000, 0x0400, 0x17, 0x80},  //  7  |     7   |     5   | PA7
46
       usw.                                      
47
    };
48
    
49
    constexpr uint8_t ANZAHLPINS = ( sizeof(baseAddr) / sizeof(baseAddr[0]) ) ;
50
  }
51
52
----------------------------------------------------------------------------
53
54
AVRxDB_Pin.h (wird in main.cpp inkludiert)
55
56
#pragma once
57
 
58
namespace
59
{ 
60
  using Register = volatile uint8_t *;
61
  
62
  #if defined(__AVR_AVR32DB32__) || defined(__AVR_AVR64DB32__) || defined(__AVR_AVR128DB32__)
63
    #include "include\Pin\AVRxDB32_PinRegister.h" 
64
    #include "include\Pin\AVRxDB32_PinNamen.h"
65
  #elif defined(__AVR_AVR32DB48__) || defined(__AVR_AVR64DB48__) || defined(__AVR_AVR128DB48__)
66
    #include "include\Pin\AVRxDB48_PinRegister.h"  
67
    #include "include\Pin\AVRxDB48_PinNamen.h"
68
  #elif defined(__AVR_AVR32DB64__) || defined(__AVR_AVR64DB64__) || defined(__AVR_AVR128DB64__)
69
    #include "include\Pin\AVRxDB64_PinRegister.h"  
70
    #include "include\Pin\AVRxDB64_PinNamen.h"  
71
  #else
72
    #error "Your board has no AVRxDB controller!".
73
  #endif
74
75
  #define INLINE inline __attribute__((always_inline))
76
  
77
  // ------------------ VPORTs ------------------ //    
78
  // Direction
79
  constexpr Register regVPORTdir(const uint8_t pin)   { using namespace Pins::Addr; return (Register) (baseAddr[pin].vport + addrOffset.vDir); }
80
  
81
  // Output Level
82
  constexpr Register regVPORTout(const uint8_t pin)   { using namespace Pins::Addr; return (Register) (baseAddr[pin].vport + addrOffset.vOut); }
83
  
84
  // Input Level Status
85
  constexpr Register regVPORTin(const uint8_t pin)    { using namespace Pins::Addr; return (Register) (baseAddr[pin].vport + addrOffset.vIn); }
86
  
87
  // Interrupt Flag
88
  constexpr Register regVPORTflag(const uint8_t pin)  { using namespace Pins::Addr; return (Register) (baseAddr[pin].vport + addrOffset.vFlag); }
89
90
  usw.
91
92
}
93
94
----------------------------------------------------------------------------
95
96
Daraus kann man dann sowas machen und weiterverwenden für Objektinstanzen und deren Methoden.
97
98
void INLINE setOn() {
99
  *regVPORTout(pin) = *regVPORTout(pin) | getMask(pin);
100
}
101
102
void INLINE setOff() {
103
  *regVPORTout(pin) = *regVPORTout(pin) & ~getMask(pin);
104
}
105
void INLINE toggle() {
106
  *regVPORTin(pin) = *regVPORTin(pin) | getMask(pin);
107
}

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Veit D. schrieb:
> #include "include\Pin\AVRxDB32_PinRegister.h"

Da gehören / hin, also Slashes.  Auch dann wenn man mit Windows 
arbeitet.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

war mir nicht bewusst. Nur Windows stört sich daran nicht, weil das ist 
Standard unter Windows.

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Veit D. schrieb:
> Nur Windows stört sich daran nicht, weil das ist
> Standard unter Windows.

Aber weder für C noch für C++.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

dann muss ich aber jetzt einmal blöd fragen, warum meckert dann der 
Compiler nicht? Also man kann sich ja nicht über etwas aufregen, was 
angeblich falsch sein soll, was jedoch funktioniert. Ich verstehe das 
Problem das mein Code nicht auf Linux kompilierbar ist, dass habe ich 
verstanden. Nur dann muss der Compiler meckern und die Vorgabe machen, 
wenn es überall gleichgezogen kompilieren sollen.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Veit D. schrieb:
> warum meckert dann der Compiler nicht?

Kann ich dir nicht sagen.  Weil \P und \A keine Control-Codes sind und 
auf P bzw. A abgebildet werden? Frag die Compilerbauer.

> Also man kann sich ja nicht über etwas aufregen, was angeblich
> falsch sein soll, was jedoch funktioniert.

Die Logik ist:

Code is korrekt => Code funktioniert

und

Code funktioniert nicht => Code ist nicht korrekt

"Code is inkorrekt" ist also nicht hinreichend dafür, dass er nicht 
funktioniert.  Die falsche Folgerung

Code funktioniert => Code ist korrekt

ist wohl der mit Abstand am weitesten verbreitete Denkfehler im 
Softwarebereich.  So auch hier.  Aber spätestens wenn du ein (anderes) 
Problem hast und jemandem den Code gibts, um das Problem 
nachzuvollziehen, kann der Fehler akut werden.  Zum Beipiel wenn dein 
Kollege unter Linux, HPUX, SunOS, ... arbeitet.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

Was ist \P und \A ?

Ich ahne worauf die Diskussion hinauslaufen wird.
Ich kürze das ab.
So richtig bewusst provokant aus der Hüfte geschossen. :-)
Warum ist Linux unfähig den Backslash im Pfad zu verstehen?
Ich befürchte, es könnte eine lange Unterhaltung werden. ;-)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Veit D. schrieb:
> Warum ist Linux unfähig den Backslash im Pfad zu verstehen?

Warum ist Windows unfähig den Slash im Pfad zu verstehen?

von Rolf M. (rmagnus)


Lesenswert?

Johann L. schrieb:
> Veit D. schrieb:
>> warum meckert dann der Compiler nicht?
>
> Kann ich dir nicht sagen.  Weil \P und \A keine Control-Codes sind und
> auf P bzw. A abgebildet werden? Frag die Compilerbauer.

Das spielt für #include-Direktiven keine Rolle.

> Die falsche Folgerung
>
> Code funktioniert => Code ist korrekt
>
> ist wohl der mit Abstand am weitesten verbreitete Denkfehler im
> Softwarebereich.

Wohl war.

Veit D. schrieb:
> Was ist \P und \A ?

Nichts, das war ja der Punkt. Es gibt in C gewisse mit \ eingeleitete 
Sequenzen wie \n für ein Newline. \P und \A gibt es aber nicht.
Bei #include gibt's davon aber extra eine Ausnahme. Man kann also z.B. 
schreiben:
1
#include "a\b.txt"
um eine Datei namens a\b.txt einzubinden, aber nicht:
1
fopen("a\b.txt", "rt");
um eine solche Datei zu öffnen, da \b das Backspace-Zeichen ist. Damit 
wird die Datei dann also nicht gefunden. Und wenn man es mit a\c.txt 
versucht, gibt's gar einen Fehler vom Compiler, da es keine 
Escape-Sequenz \c gibt.

> Warum ist Linux unfähig den Backslash im Pfad zu verstehen?

Verstehen tut es den durchaus, aber eben als Escape-Zeichen und nicht 
als Verzeichnis-Trenner. So war das in C und unter unixoiden Systemen 
schon lange, bevor Windows, oder auch DOS, überhaupt existiert hat.

: Bearbeitet durch User
von Norbert (der_norbert)


Lesenswert?

Veit D. schrieb:
> Warum ist Linux unfähig den Backslash im Pfad zu verstehen?

Linux ist sehr wohl fähig Backslashes im Pfad nicht nur zu erkennen. 
sondern sie zudem korrekt zu benutzen.
1
ls "Peter Pan"
1
ls Peter\ Pan

funktioniert zB. beides. Und ja, man kann selbstverständlich auch ASCII 
Ctrl-Chars unterbringen, sollte man dieses seltsame Bedürfnis verspüren.

Es liegt an Windows, dass man ihm jeden Stuss vorsetzen kann.
Slash, Backslash, Groß, Klein, Windows frisst eine Menge Zeug und es 
macht eine Menge irrer Annahmen um das Geraffel irgendwie zum Laufen zu 
bringen.
Altlasten aus der Zeit der Altvorderen.

von Harald K. (kirnbichler)


Lesenswert?

Das schöne bei Linux ./. was-anderes-Flamewars ist die extreme Einsicht, 
die die beiden Seiten jeweils aufbringen, indem sie ihre Seite der Dinge 
als die einzig mögliche und einzig richtige darstellen.

So funktionieren Religionen.

von Norbert (der_norbert)


Lesenswert?

Harald K. schrieb:
> Das schöne bei Linux ./. was-anderes-Flamewars ist die extreme
> Einsicht,
> die die beiden Seiten jeweils aufbringen, indem sie ihre Seite der Dinge


> als die einzig mögliche und einzig richtige darstellen.
Macht keiner. ›Einzig möglich‹ ist ja durch Windows schon widerlegt.
›Einzig richtig‹ wohl auch nicht, das ist durch andere Systeme 
widerlegt.


Wenn man aber der Ansicht ist, dass es egal ist ob man ›\‹ oder ›/‹ oder 
gar beides bunt gemischt benutzt, dann stimme ich dir zu.
Wenn man "ABC, "abc","AbC",… als gleich ansieht, dann stimme ich dir zu.

PS. Ist der Explorer mittlerweile in der Lage mit einer einzelnen 
Operation die Groß/Kleinschreibung (umbenennen) einer Datei 
durchzuführen, oder braucht es dazu immer noch zwei Operationen?

Edit:

Norbert schrieb:
> Altlasten aus der Zeit der Altvorderen.

Damit meine ich übrigens, dass in den Anfängen zunächst erst 8.3(immer 
Groß) zulässig war. Dann ging's etwas länger (zumindest stark begrenzt 
länger), dann kam Groß/Klein dazu, dann ging's noch länger, dann gab's 
eine Zeit während der die Dateinamen/Pfadnamen Begrenzungen nicht sauber 
in den SDKs definiert waren, usw.
Irgendwann zeitlich dazwischen — das wird sicherlich jemand besser 
wissen als ich — konnte man plötzlich ›\‹ UND ›/‹ verwenden.

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Der / als Pfad-Trenner ist eine Degign-Entscheidung von C/C++.  Hätte 
man natürlich auch was anderes nehmen können wie @.

Jedenfalls war die Entscheidung, die Quellen unabhängig vom Host-System 
zu halten, eine gute.

von Norbert (der_norbert)


Lesenswert?

Johann L. schrieb:
> Der / als Pfad-Trenner ist eine Degign-Entscheidung von C/C++.
> Hätte
> man natürlich auch was anderes nehmen können wie @.
>
> Jedenfalls war die Entscheidung, die Quellen unabhängig vom Host-System
> zu halten, eine gute.

Na ja, aber Veit hat hier 
Beitrag "Re: GCC Statische initialisierung." schon die richtige 
Frage gestellt.

Womöglich wird der ganze (Pfad-)String (ob nun richtig oder falsch) 
einfach an das Betriebssystem zur Öffnung übergeben und auf etwaige 
Fehler überprüft. Und da liefert Windows halt ein OK. Das wäre jetzt 
zumindest meine Vermutung. Ergo prüft ›C‹** evtl. nicht wirklich was da 
im (Pfad-)String drin steht.

** Wohl eher der Präprozessor

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Norbert schrieb:
> Na ja, aber Veit hat hier
> Beitrag "Re: GCC Statische initialisierung." schon die richtige
> Frage gestellt.

Und eine Antwort habe ich bereits hier gegeben:

Beitrag "Re: GCC Statische initialisierung."

Was GCC betrifft, so ist der entsprechende Code in der libcpp:

https://gcc.gnu.org/git/?p=gcc.git;a=tree;f=libcpp

Für alle, die es wirklich interessiert.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Johann L. schrieb:
> Veit D. schrieb:
>> Warum ist Linux unfähig den Backslash im Pfad zu verstehen?
>
> Warum ist Windows unfähig den Slash im Pfad zu verstehen?

Ist es ja gar nicht.  Schon MS-DOS 3.x konnte den Slash intern als 
Pfad-Trenner benutzen.  Lediglich command.com und dessen Nachfolger 
haben den Slash als Options-Zeichen benutzt (statt Bindestrich in Unix) 
und brauch(t)en daher den Backslash.

Intern (auf Ebene der Systemaufrufe) funktioniert der Vorwärtsstrich, 
weshalb man im Sinne der Portabilität gut daran tut, diesen auch bei 
Windows durchweg innerhalb von Programmen zu verwenden.  Ganz nebenbei 
umgeht man dadurch potenzielle Probleme der Fehlinterpretation von durch 
Backslash eingeleiteten Escape-Sequenzen – nicht nur bei C, auch andere 
Sprachen haben dieses Konzept ja.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Veit D. schrieb:
> Warum ist Linux unfähig den Backslash im Pfad zu verstehen?

Der Slash als Trenner in Pfaden existiert bereits seit 1970 (UNIX), da 
gab es noch keine Betriebssysteme von Microsoft. Warum MS sich dann für 
den Backslash entschied, könnte an Jörgs Erklärung liegen: MS hat den 
Slash für Kommando-Optionen genutzt - vermutlich, bevor überhaupt 
Unterverzeichnisse in MS-DOS eingeführt wurden. Das war damals schon 
eine krasse Fehlentscheidung.

Warum unterstützt der GCC Backslashes in Include-Pfaden unter Windows? 
Wahrscheinlich aus Mitleid - um es vorsichtig auszudrücken.

P.S.

Browser wie Edge, Chrome und Firefox unterstützen auch URLs mit 
Backslashes wie zum Beispiel:
1
https:\\www.mikrocontroller.net\topic\580316

Obwohl Backslashes meines Wissens nach in URLs gar nicht so 
funktionieren dürfen - siehe RFC 3986.

: Bearbeitet durch Moderator
von Rolf M. (rmagnus)


Lesenswert?

Johann L. schrieb:
> Der / als Pfad-Trenner ist eine Degign-Entscheidung von C/C++.

Nö. C interessiert sich überhaupt nicht für Pfadtrenner. Bei #include 
sagt es einfach, dass es systemspezifisch ist, wie der dort angegebene 
Name in einen Dateinamen übersetzt wird oder ob da überhaupt echte 
Dateien dahinter stehen.

> Jedenfalls war die Entscheidung, die Quellen unabhängig vom Host-System
> zu halten, eine gute.

Das ist aber keine Entscheidung von C oder C++.

Jörg W. schrieb:
> Johann L. schrieb:
>> Veit D. schrieb:
>>> Warum ist Linux unfähig den Backslash im Pfad zu verstehen?
>>
>> Warum ist Windows unfähig den Slash im Pfad zu verstehen?
>
> Ist es ja gar nicht.  Schon MS-DOS 3.x konnte den Slash intern als
> Pfad-Trenner benutzen.  Lediglich command.com und dessen Nachfolger
> haben den Slash als Options-Zeichen benutzt (statt Bindestrich in Unix)
> und brauch(t)en daher den Backslash.

Nicht nur das, sondern sie haben auch erlaubt, das trennende Leerzeichen 
zwischen Programmname und Parameter weglassen zu können, wie z.B.:
1
dir/p
Wäre ein trennendes Leerzeichen erforderlich, dann wäre es auch kein 
Problem gewesen, den / als Verzeichnistrenner in command.com zu 
unterstützen.

> Warum unterstützt der GCC Backslashes in Include-Pfaden unter Windows?
> Wahrscheinlich aus Mitleid - um es vorsichtig auszudrücken.

Ich vermute eher, weil die anderen Compiler für Windows es auch können 
und weil man, um es nicht zu können, extra zusätzlichen Code einbauen 
müsste. Tut man nichts dagegen, dann funktioniert es automatisch.

: Bearbeitet durch User
von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Frank M. schrieb:
> Warum unterstützt der GCC Backslashes in Include-Pfaden unter Windows?
> Wahrscheinlich aus Mitleid - um es vorsichtig auszudrücken.

Naja es macht schon Sinn das native Pfad-Format des jeweiligen Host-OS 
zu verstehen. Diskutabel ist, ob bei Ausgaben auch (standardmäßig) das 
native Format verwendet werden sollte, beim Compiler also z.B. in den 
Pfaden der Debug-Informationen.

CMake unterstützt z.B. bei manchen Eingabe-Pfaden auch unter Windows 
ausschließlich Unix-Trennzeichen, während z.B. VS Code den Pfad zum 
aktuellen Projekt in der Kommandozeilen-Substitution mit Backslashes 
generiert, sodass man das nicht direkt an CMake übergeben kann - sehr 
ungünstig!

Außerdem sieht "C:/Users/ich/..." schon irgendwie komisch aus 😉

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


Lesenswert?

Frank M. schrieb:
> Warum unterstützt der GCC Backslashes in Include-Pfaden unter Windows?

Ich denke, die Erklärung ist viel einfacher, als die meisten vermuten: 
weil ihn das gar nicht interessiert.  Alles zwischen den Spitzklammern 
oder Anführungszeichen nach #include wird am Ende an das OS 
weitergereicht.  Deshalb funktionieren die Vorwärtsstriche eben auch 
unter Windows (und sowas wie #include <sys/stat.h> gab es meiner 
Erinnerung nach schon in den ersten Versionen von Turbo-C), denn die 
Syscalls dort kommen ja damit klar.

von Zino (zinn)


Lesenswert?

ISO 9899-1999 6.4.7:

If the characters ', \, ", //, or /* occur in the sequence between the < 
and > delimiters, the behavior is undefined. Similarly, if the 
characters ', \, //, or /* occur in the sequence between the " 
delimiters, the behavior is undefined.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Niklas G. schrieb:
> Außerdem sieht "C:/Users/ich/..." schon irgendwie komisch aus

Noch seltsamer sieht es ja als URL aus, z.B. im Browser:

file:///C/Users/ich/...

Zino schrieb:
> ISO 9899-1999 6.4.7:
>
> If the characters ', \, ", //, or /* occur in the sequence between the <
> and > delimiters, the behavior is undefined. Similarly, if the
> characters ', \, //, or /* occur in the sequence between the "
> delimiters, the behavior is undefined.

Hmm, interessant. Das heißt, dass man bei #include den \ als 
Verzeichnistrenner in C eigentlich gar nicht verwenden darf.
Die praktisch gleiche Regel gibt es in C++ übrigens auch.

: Bearbeitet durch User
von Zino (zinn)


Lesenswert?

Und da es hier ja eigentlich um C++ geht noch 2.8 aus ISO 14882:

If either of the characters ’ or \, or either of the character sequences 
/* or // appears in a q-char-sequence or a h-char-sequence, or the 
character " appears in a h-char-sequence, the behavior is undefined

Wohlgemerkt: "undefined", nicht "implementation-defined"!

D.h.
1
#include <sys\stat.h>
ist in C++ (anders als in C99) erlaubt, wohingegen die Bedeutung von
1
#include "sys\stat.h"
undefiniert ist.

: Bearbeitet durch User
von Zino (zinn)


Lesenswert?

Allerdings ist
1
#include <sys\stat.h>
in C++ zwar erlaubt, aber nicht portabel.

von Zino (zinn)


Lesenswert?

Und in C++23 ist es wieder anders (das Zitat oben war für C++03):

The appearance of either of the characters ’ or \ or of either of the 
character sequences /* or // in a q-char-sequence or an h-char-sequence 
is conditionally-supported with implementation-defined semantics, as is 
the appearance of the character " in an h-char-sequence.[13]

13) Thus, a sequence of characters that resembles an escape sequence can 
result in an error, be interpreted as the character corresponding to the 
escape sequence, or have a completely different meaning, depending on 
the implementation.

von Rolf M. (rmagnus)


Lesenswert?

Zino schrieb:
> Allerdings ist
> #include <sys\stat.h>
> in C++ zwar erlaubt, aber nicht portabel.

Portabel ist in dem Sinne aber sowieso nichts, außer den 
Standard-Header-Namen, und die enthalten soweit ich weiß keine 
Verzeichnistrenner.

: Bearbeitet durch User
von Zino (zinn)


Lesenswert?

Z.B. der Header sys/stat.h wird von IEEE 1003 definiert. Mit /.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Zino schrieb:
> Z.B. der Header sys/stat.h wird von IEEE 1003 definiert. Mit /.

Mit Standard-Header meinte ich jetzt explizit die aus dem C- und dem 
C++-Standard. Bei POSIX sieht's natürlich wieder anders aus. Windows ist 
aber nicht POSIX-konform.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Rolf M. schrieb:
> Windows ist aber nicht POSIX-konform.

Windows war es aber mal ab Windows NT - hauptsächlich, um überhaupt noch 
Aufträge von der US-Regierung zu erhalten. Die Posix-Konformität wurde 
aber mit Windows XP bzw. Windows 2003 wieder entfernt.

Quellen:

- https://de.wikipedia.org/wiki/POSIX-Subsystem
- https://en.wikipedia.org/wiki/Microsoft_POSIX_subsystem 
(ausführlicher)

: Bearbeitet durch Moderator
von Zino (zinn)


Lesenswert?

Und auch die Visual-C++-Doku verlangt einen / in sys/stat.h für Windows:

https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/stat-functions?view=msvc-170

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Johann L. schrieb:
> Veit D. schrieb:
>> Warum ist Linux unfähig den Backslash im Pfad zu verstehen?
>
> Warum ist Windows unfähig den Slash im Pfad zu verstehen?

Sorry, aber genau diese Frage stellt sich unter Windows nicht. Es kann 
mit Slash und Backslash umgehen. Kann man auch in der DOS-Box bzw. im 
cmd prompt ausprobieren. Ansonsten hätte ich den include Pfad wie 
gezeigt überhaupt nicht schreiben können und ich hätte nicht diese 
provokante Frage gestellt.

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Hallo,

zur Beruhigung der Massen.
Da ich nur unter Windows programmiere fiel mir das eben noch nicht auf 
bzw. dachte es wäre egal. Bewusst um jemanden zu ärgern habe ich das 
jedenfalls nicht gemacht. Ich werde in Zukunft / verwenden als 
Pfadtrenner. Glücklicherweise kann Windows damit umgehen. ;-) Was man 
nicht alles als Windows User macht damit Linuxer glücklich sind. ;-)

von Zino (zinn)


Lesenswert?

Veit D. schrieb:
> Was man nicht alles als Windows User macht damit Linuxer glücklich sind. ;-)

Backslashes bei #include waren schon undefined behavior bevor es 
Linux(er) gab.

von Veit D. (devil-elec)


Lesenswert?

Zino schrieb:
> Veit D. schrieb:
>> Was man nicht alles als Windows User macht damit Linuxer glücklich sind. ;-)
>
> Backslashes bei #include waren schon undefined behavior bevor es
> Linux(er) gab.

Irgendwas stimmt an der Aussage nicht.
a) unter Windows sind Backslash Standard
b) wird der Pfad von include vom Dateisystem vom OS aufgelöst

Ansonsten würde es mit Backslash unter Windows und include nicht 
funktionieren. Man kann nicht etwas unverständliches dem Compiler 
übergeben, der wiederum auf das Dateisystem mit falscher Pfadangabe 
zugreift. Irgendwann wäre mir ein "undefined behavior" aufgefallen in 
den Jahren. Weil ansonsten hätte es direkt beim Kompilieren eine 
Fehlermeldung gegeben mit Pfad nicht gefunden o.ä.. Irgendwelche 
versteckten Fehler zur Laufzeit kann es dabei nicht geben.

Der zitierte Spruch war auch anders gemeint. Windows User haben mit 
Backslash und include keine Problem zu erwarten. Im include gibt es 
keine Escape-Zeichen. Das Problem mit Backslash beginnt, wie von Johann 
richtig erkannt, wenn man es mit Backslash unter Linux verwenden möchte. 
Dann muss man alle Pfadangaben korrigieren. Das heißt ich als Windows 
User korrigiere die Pfadangaben erstmal primär für potentielle Linuxer. 
Weil sich das bei mir nicht auswirkt. Das war der Witz im Spruch. ;-) 
Bei mir korrigiert das OS Dateisystem den Slash zurück in Backslash für 
die Pfadangabe und Dateizugriff. Weil unter Linux ist Slash Standard und 
unter Windows ist Backslash Standard. Irgendein OS User muss die Hürde 
nehmen.
Weil aber ein Escape-Zeichen den Backslash benötigt, verwendet man beim 
Programmieren besser Slash als Pfadtrenner. Das heißt der Windows User 
muss die Gedanken zusammennehmen.

: Bearbeitet durch User
von Zino (zinn)


Lesenswert?

Veit D. schrieb:
> Irgendwas stimmt an der Aussage nicht.
> a) unter Windows sind Backslash Standard
> b) wird der Pfad von include vom Dateisystem vom OS aufgelöst

In den C- und C++-Standards ist das, was man hinter #include in spitzen 
Klammern oder Anführungszeichen angibt, gar nicht als Pfadname 
spezifiziert. Also sind Deine beiden Beobachtungen für ISO 9899 (C) und 
ISO 14882 (C++) nicht relevant.

Vielmehr ist das "Argument" von #include irgendetwas, das auf 
implementationsspezifische Weise auf einen Header oder eine Quelldatei 
abgebildet wird. Z.B. braucht keine Datei namens stdio.h zu existieren 
solange
1
#include <stdio.h>
genau das tut, was die Spezifikation verlangt.

von Zino (zinn)


Lesenswert?

Veit D. schrieb:
> Das Problem mit Backslash beginnt, wie von Johann
> richtig erkannt, wenn man es mit Backslash unter Linux verwenden möchte.

Warum sollte man das wollen? Als C erfunden wurde, hat keiner 
Backslashes zur Trennung von Komponenten in Pfadnamen verwendet.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Veit D. schrieb:
> Das heißt ich als Windows User korrigiere die
> Pfadangaben erstmal primär für potentielle Linuxer.

Du machst es in erster Linie für dich selbst (falls du den Code für dich 
selbst schreibst).

Oder wenn du in nem Forum was zum Code fragst oder nen Bugreport machst.

Jedenfalls scheint das Thema dich ziemlich aufzureiben...

von Zino (zinn)


Lesenswert?

Die Programmiersprachen C und C++ (ohne Beachtung der 
Standardbibliotheken) kennen das Konzept "Pfadnamen" gar nicht. Es gibt 
auch keine Unterverzeichnisse oder ähnliches, keine Komponenten von 
Pfadnamen, keine Trennzeichen. Wie Dein Lieblingsbetrübssystem Pfadnamen 
definiert, ist also völlig irrelevant. Es gelten die Regeln, die ISO 
9899 bzw. ISO 14882 für #include festlegen.
1
#include "include\Pin\AVRxDB32_PinRegister.h"
ist erst seit (ungefähr, mein Archiv ist lückenhaft) C++23 nicht mehr 
völlig undefiniert.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Veit D. schrieb:
> b) wird der Pfad von include vom Dateisystem vom OS aufgelöst

Damit ist trotzdem nicht völlig eindeutig, ob
1
#include "subdir\neuedatei.h"

nun als "neuedatei.h" in "subdir" interpretiert wird oder als 
"subdir^Jeuedatei.h", denn der Backslash leitet in einem String eine 
Escape-Sequenz ein.

Deshalb ist es eben, je nach Standard, entweder undefined behaviour oder 
implementation defined.

von Zino (zinn)


Lesenswert?

Aber "..." bei #include "..." ist gar kein string-literal sondern ein 
Anführungszeichen gefolgt von einer q-char-sequence gefolgt von einem 
Anführungszeichen. Interessanter wird es bei
1
#define N "subdir\neuedatei.h"
2
#include N
Da gilt zunächst die Syntax von pp-token (also hier string-literal) und 
dann die von q-char-sequence.

von Rolf M. (rmagnus)


Lesenswert?

Veit D. schrieb:
>> Backslashes bei #include waren schon undefined behavior bevor es
>> Linux(er) gab.
>
> Irgendwas stimmt an der Aussage nicht.
> a) unter Windows sind Backslash Standard

Das ist für C nicht relevant. Wie schon ein paar mal erwähnt, gab es 
Windows noch gar nicht, als das für C definiert wurde, und kein Mensch 
hat \ als Verzeichnistrenner verwendet.

> b) wird der Pfad von include vom Dateisystem vom OS aufgelöst

Nein, er wird auf impelmetationsspezifische Weise vom Compiler in 
Dateinamen oder ggf. auch was anderes transformiert, um damit auf den 
Header zuzugreifen. Diese Transformation kann natürlich auch daraus 
bestehen, dass der Compiler den Namen 1:1 an die Betriebssystem-Funktion 
weitergibt, aber das ist dann Sache des Compilers, nicht des Standards.

> Ansonsten würde es mit Backslash unter Windows und include nicht
> funktionieren. Man kann nicht etwas unverständliches dem Compiler
> übergeben, der wiederum auf das Dateisystem mit falscher Pfadangabe
> zugreift. Irgendwann wäre mir ein "undefined behavior" aufgefallen in
> den Jahren.

Undefined behavior heißt nicht, dass der Standard verlangt, dass es 
nicht funktioniert, sondern nur, dass aus Sicht des Standards beliebiges 
passieren kann, wenn du es versuchst. Ab dem Zeitpunkt, zu dem du 
undefiniertes Verhalten hervorrufst, darf der Compiler machen, was immer 
er will, und das nicht nur mit dem spezifischen Konstrukt, der es 
hervorgerufen hat, sondern mit dem gesamten Programm. Wie gesagt heißt 
das nicht, dass etwas komisches passieren muss, aber es darf, und 
der Compiler ist dann immer noch standardkonform.

> Weil ansonsten hätte es direkt beim Kompilieren eine
> Fehlermeldung gegeben mit Pfad nicht gefunden o.ä.. Irgendwelche
> versteckten Fehler zur Laufzeit kann es dabei nicht geben.

Auch das entspricht nicht dem, was Undefined Behavior bedeutet.

> Der zitierte Spruch war auch anders gemeint. Windows User haben mit
> Backslash und include keine Problem zu erwarten. Im include gibt es
> keine Escape-Zeichen. Das Problem mit Backslash beginnt, wie von Johann
> richtig erkannt, wenn man es mit Backslash unter Linux verwenden möchte.
> Dann muss man alle Pfadangaben korrigieren. Das heißt ich als Windows
> User korrigiere die Pfadangaben erstmal primär für potentielle Linuxer.

Das eigentlich nicht direkt mit Linux zu tun. Es gibt eigentlich nur die 
folgenden Dinge, die wesentlich sind:

- Verwendest du /, dann ist das nach C-Standard zwar 
implementationsspezifisch (was die headernamen aber eh immer sind), aber 
es ist ok. Es funktioniert auf so ziemlich allem, was den Compiler 
ausführen kann, und das geht auch über Linux hinaus (z.B. MacOS, 
FreeBSD, Solaris, …)

- Wenn du dagegen \ verwendest, ist das nach C-Standard und C++ vor 
C++23 offiziell undefiniertes Verhalten. Es funktioniert außerdem 
ausschießlich unter Windows. Irgendeinen Vorteil gegenüber der anderen 
Variante hat es nicht.
Dazu kommt noch, dass das mit dem \ ja so nur bei #include funktioniert. 
Beim Öffnen von Dateien dagegen tut's dann (mit String literals) so 
nicht mehr.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Rolf M. schrieb:
> Beim Öffnen von Dateien dagegen tut's dann (mit String literals) so
> nicht mehr.

… oder man müsste dann generell alle Backslashes doppelt notieren, das 
wäre wieder sicher. Vorwärtsstriche sind dann sicherlich die einfachere 
(und besser lesbare) Variante. ;-)

: Bearbeitet durch Moderator
Beitrag #7939576 wurde vom Autor gelöscht.
von Zino (zinn)


Lesenswert?

Illustration von "undefined behavior":
http://www.catb.org/jargon/html/N/nasal-demons.html

von Rolf M. (rmagnus)


Lesenswert?

Zino schrieb:
> Illustration von "undefined behavior":
> http://www.catb.org/jargon/html/N/nasal-demons.html

Ja, kenn ich noch. Ich war zwar glaub noch nicht dabei, als das 
entstanden ist, aber kurz danach. Hab selbst oft genug in comp.lang.c 
"Neulingen" von nasalen Dämonen erzählt … mann, bin ich alt. 🤔

von Zino (zinn)


Lesenswert?

Rolf M. schrieb:
> mann, bin ich alt. 🤔

Wem sagst Du das. Ich habe an ISO 9899:1990 mitgeschrieben.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

Danke für die umfangreichen Antworten, die erstmal verdaut werden 
müssen.

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.