GCC Statische initialisierung.

#7936211
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
}
Persönliche Seite #7936220
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
#7936242
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
Beitrag #7938480 wurde vom Autor gelöscht.
#7938483
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
}
#7938983
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.

Persönliche Seite #7938986
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.

#7939068
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
#7939072
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.

#7939077
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
#7939142
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
Persönliche Seite #7939152
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
Moderator Persönliche Seite #7939167
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.

Moderator Persönliche Seite #7939194
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
#7939201
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
Persönliche Seite #7939205
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 😉

Moderator Persönliche Seite #7939209
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.

#7939213
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
#7939216
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
#7939221
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]

  1. 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.
Moderator Persönliche Seite #7939236
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:

: Bearbeitet durch Moderator
#7939315
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
#7939318
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. ;-)

#7939427
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
#7939459
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.

Persönliche Seite #7939465
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...

#7939475
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
Moderator Persönliche Seite #7939485
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.

#7939556
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
Moderator Persönliche Seite #7939571
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.

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