ich habe mal eine Frage zum Dateityp, den man als Index verwendet, wie z.B.
1
uint16_tarray[100];//Array mit 100 Elementen
2
uint16_tTest;//Testvariable
3
uint8_tFeld;// Parameter für die Feldauswahl
4
5
Feld=18;
6
test=array[Feld];
Bisher habe ich immer den Dateityp von "Feld" so gewählt, daß der größte Index damit verwendet werden kann (im Fall von 100 wäre das eine 8-Bit-Variable).
Kann man prinzipiell als "Feld" eine 32-Bit Variable verwenden, auch wenn die Anzahl der Felder nur 100 ist (ohne Cast)? Oder geht das nur mit Cast?
1
uint16_tarray[100];//Array mit 100 Elementen
2
uint16_tTest;//Testvariable
3
uint32_tFeld;// Parameter für die Feldauswahl
4
5
Feld=18;
6
test=array[(uint8_t)Feld];
Grund für die Frage: Da ich einen STM32 verwende, möchte ich weitestmöglich 32-Bit-Variablen verwenden. Dann muß der Mikrocontroller nicht ständig aus 32-Bit-Registern 8-Bit-Werte machen.
Grund für die Frage: Da ich einen STM32 verwende, möchte ich
weitestmöglich 32-Bit-Variablen verwenden. Dann muß der Mikrocontroller
nicht ständig aus 32-Bit-Registern 8-Bit-Werte machen.
So lange du nicht einmal die Grundlagen von C verinnerlicht hast, musst du dir darum wirklich keine Gedanken machen.
Grund für die Frage: Da ich einen STM32 verwende, möchte ich
weitestmöglich 32-Bit-Variablen verwenden. Dann muß der Mikrocontroller
nicht ständig aus 32-Bit-Registern 8-Bit-Werte machen.
Das sind Microoptimierungen, die selten sinnvoll sind. Der Index von Arrays wird intern als int behandelt bzw. in diesen konvertiert. Vorzeichenbehaftet.
Das sind Microoptimierungen, die selten sinnvoll sind. Der Index von
Arrays wird intern als int behandelt bzw. in diesen konvertiert.
Vorzeichenbehaftet.
Danke für die Antwort.
Aber generell (abgesehen von Array-Indexen) ist die Verwendung von Variablen in Bitbreitengröße des µC sinnvoll? Z.B. für Variablen mit Inhalt TRUE und FALSE (1 und 0)? Bisher habe ich dafür 8-Bit-Variablen verwendet. Ist aber historisch bedingt, da ich früher mit 8-Bit-µC gearbeitet habe.
Aber generell (abgesehen von Array-Indexen) ist die Verwendung von
Variablen in Bitbreitengröße des µC sinnvoll? Z.B. für Variablen mit
Inhalt TRUE und FALSE (1 und 0)? Bisher habe ich dafür 8-Bit-Variablen
verwendet. Ist aber historisch bedingt, da ich früher mit 8-Bit-µC
gearbeitet habe.
Darum kümmert sich der Compiler und das Ergebnis ist u.A. abhängig von den eingestellten optimierungs-Funktionen.
Also hör auf, dir wegen sowas nen Kopf zu machen, und lern besser C!
Dann verstehst du irgendwann vielleicht auch, wann es sinnvoll ist, sich um sowas überhaupt Gedanken zu machen.
ich habe mal eine Frage zum Dateityp, den man als Index verwendet, wie
z.B.uint16_t array[100]; //Array mit 100 Elementen
uint16_t Test; //Testvariable
uint8_t Feld; // Parameter für die Feldauswahl
Feld = 18;
test = array[Feld];
By the way: Test und test passen nicht zusammen, das sind 2 verschiedene Variablen.
Kann man prinzipiell als "Feld" eine 32-Bit Variable verwenden, auch
wenn die Anzahl der Felder nur 100 ist (ohne Cast)?
Kann man
Grund für die Frage: Da ich einen STM32 verwende, möchte ich
weitestmöglich 32-Bit-Variablen verwenden. Dann muß der Mikrocontroller
nicht ständig aus 32-Bit-Registern 8-Bit-Werte machen.
Der Index von Arrays wird intern als int behandelt bzw. in diesen
konvertiert. Vorzeichenbehaftet.
Und für andere Fälle wo das nicht zutrifft kostet die Umwandlung keine bzw. vernachlässigbar wenig Zeit.
Dazu gibt es Datentypen wie:
int_fast8_t
uint_fast8_t
Damit bekommst du die schnellste Variante, die mindestens 8 Bit groß ist. Ich benutze sie z.B. in Arduino Code, der auf unterschiedlichen Mikrocontrollern lauffähig sein soll.
Nicht ganz korrekt. Wie schon genannt worden ist, ist ein Feldindex vorzeichenbehaftet: es ist beispielsweise legitim, dass man einen Zeiger schon ein Element weitergezählt hat und dann mit
1
ptr[-1]
auf das letzte noch belegte Element zugreift. size_t ist aber vorzeichenlos.
Posix hat dafür extra noch einen ssize_t eingeführt, mit dem man insbesondere Fehler-Rückgaben als -1 realiseren kann, auch wenn der Wert ansonsten "eigentlich" size_t wäre.
Die Cortex-M CPU kann beim Zugriff auf das RAM 8 und 16 Bit Daten im selben Abwasch konvertieren. Für Zähler von for-Schleifen würde allerdings immer 32 Bit (oder int_fast8_t) nehmen, weil diese nur in einem Register liegen, da findet kein RAM Zugriff statt.
In den meisten Fällen schreibt man in C KEINE maschinenoptimierte oder gar abhängigen Code! Im Gegenteil, man strebt eher Portierbarkeit an.
Z.B. für Variablen mit
Inhalt TRUE und FALSE (1 und 0)?
Dafür gibt es bool. Der Compiler setzt das dann schon gescheit um.
Bisher habe ich dafür 8-Bit-Variablen
verwendet. Ist aber historisch bedingt, da ich früher mit 8-Bit-µC
gearbeitet habe.
Jaja, am besten gleich wieder Inlie Assembler, was? ;-)
Man muss sich be C ein wenig von der Maschinennähe und dem Mikromanagement lösen und das dem Compiler überlassen. In 95% der Fälle ist das ausreichend gut. Siehe oben.
Nicht ganz korrekt. Wie schon genannt worden ist, ist ein Feldindex
vorzeichenbehaftet: es ist beispielsweise legitim, dass man einen Zeiger
schon ein Element weitergezählt hat und dann mit
1
ptr[-1]
auf das letzte noch belegte Element zugreift. size_t ist aber
vorzeichenlos.
Posix hat dafür extra noch einen ssize_t eingeführt, mit dem man
insbesondere Fehler-Rückgaben als -1 realiseren kann, auch wenn der Wert
ansonsten "eigentlich" size_t wäre.
Das verstehe ich jetzt wirklich nicht. Wie kann ein Index negativ sein? Der beginnt bei 0 bis ...
Einen Fehlercode würde ich auch nicht mit dem Index verwursten.
Es gibt Tricks wie bei der Fleury USART Lib, dass man Fehlercodes ins nächste höhere Byte versteckt und dann wieder rausfiltert, aber das bleibt unsigned. Zudem der Fehlercode nichts mit dem Indexwert zu tun hat. Man gibt einen Index an um das Ergebnis aus einem Array auszulesen. Der Rückgabewert ist dann kein Index sondern der Inhalt aus dem Array. Ich würde das nicht vermischen.
Das verstehe ich jetzt wirklich nicht. Wie kann ein Index negativ sein?
Der beginnt bei 0 bis ...
Der (gültige) Index auf ein Array schon, die Berechnung des Indexes kann aber auch was mit Subtraktion zu tun haben. Darum ist dessen Berechnung und Handhabung immer signed int.
auszulesen. Der Rückgabewert ist dann kein Index sondern der Inhalt aus
dem Array. Ich würde das nicht vermischen.
Posix hat dafür extra noch einen ssize_t eingeführt, mit dem man
insbesondere Fehler-Rückgaben als -1 realiseren kann, auch wenn der Wert
ansonsten "eigentlich" size_t wäre.
Der C-Standard spezifiziert den Typ ptrdiff_t mit dem Wertebereich
[PTRDIFF_MIN, PTRDIFF_MAX]. Dieser Typ ist für Indizes (auch negative)
IMHO am besten geeignet. In realen Compilern wird er wohl immer zu
ssize_t äquivalent sein, auch wenn POSIX den Wertebereich von ssize_t
nach unten nur bis -1 garantiert.
okay, dann sind wir gedanklich schon ein paar Schritte weiter. Nämlich in der Auftrennung des Codes in hier eine Funktion zur Berechnung und da eine Funktion für den Zugriff auf das Array. Laut meiner aktuellen Überlegung würde ich denke ich immer noch keine signed Variable auf einen Arrayzugriff loslassen. Also der Datentyp vom Indexparameter wäre bei immer noch unsigned.
1
charreadArray(size_tindex)
2
{
3
}
Wie ich den Index vorher berechne steht auf einem anderen Blatt. Sind eben so meine Überlegungen dafür. Tut mir leid. ;-)
Posix hat dafür extra noch einen ssize_t eingeführt, mit dem man
insbesondere Fehler-Rückgaben als -1 realiseren kann, auch wenn der Wert
ansonsten "eigentlich" size_t wäre.
Der C-Standard spezifiziert den Typ ptrdiff_t mit dem Wertebereich
[PTRDIFF_MIN, PTRDIFF_MAX]. Dieser Typ ist für Indizes (auch negative)
IMHO am besten geeignet. In realen Compilern wird er wohl immer zu
ssize_t äquivalent sein, auch wenn POSIX den Wertebereich von ssize_t
nach unten nur bis -1 garantiert.
Hallo,
damit habe ich beim lesen auch so meine Probleme. Der Name ptrdiff_t sagt etwas aus über Differenzen. Keine Indexe. Man kann damit ggf. Indexe berechnen indem man meinetwegen +2 vor oder -3 rückwärts springt. Der Index selbst bleibt aber positiv. Ich denke hier wird etwas vermischt was man nicht vermischen sollte.
Der Index selbst bleibt aber positiv. Ich denke hier wird etwas
vermischt was man nicht vermischen sollte.
Mit dem letzten Satz stimme ich dir zu. Man sollte vorzeichenbehaftet und vorzeichenlos möglichst nicht mischen. Aber ich fand es nie sonderlich sinnvoll, den Datentyp, insbesondere seine signedness dazu zu verwenden, den möglichen Wertebereich zu beschränken. Wenn 5 - 6 nicht -1 ist, sondern 4294967295, macht's das auch nicht besser. Ich habe mir zur Regel gemacht, dass ich eine Variable nur in zwei Situationen unsigned mache:
Wenn ich den Wertebereich nach oben brauche
Wenn ich damit Bitgefummel machen will
Für alles andere nehme ich einen vorzeichenbehafteten Typ.
Ein Index ins komplette Array natürlich nicht. Aber da man bei Arrays auch oft mit Zeigern hantiert, kann man eben auch sowas wie "ich möchte ein Objekt von hier aus zurück gucken" damit machen – wobei man natürlich sicherstellen muss, dass es an der Stelle dann auch überhaupt etwas gibt.
So sind eben Indizes in C per definitionem vom Typ "int" – und zurück zur Eingangsfrage, damit wäre "int" auch der logischerweise sinnvollste Datentyp für ihn. Er ist außerdem normalerweise der Datentyp, der native "zur Maschine passt" und damit effizient ist (von kleinen Architekturen wie dem AVR abgesehen, bei denen ein "int" zwei Byte benutzt).
mein Ansatz ist eben das Indizes nie negativ sein können, deshalb widerstrebt es mir dafür signed Datentypen zu verwenden. In deinem Bsp. ist 'i' nicht negativ, also warum signed? Darauf könnte ich jeden festnageln. :-)
Gedanklich passt das nicht zusammen. Und was schon gedanklich nicht zusammenpasst sollte man auch nicht Programmieren. ;-)
Und selbst wenn man mit einem anderen Bsp. mit negativen Ergebnis ankommt, wird man das negative Ergebnis nie auf einen Arrayzugriff loslassen. Verstehst du das Problem was schon wie gesagt gedanklich nicht passt?
Mit unsigned muss man maximal den Maximalwert zur Sicherheit prüfen.
Mit signed muss man zusätzlich auf kleiner 0 prüfen. Solche Fehlerquellen sollte man sich ersparen. Es reicht ja schon zu das ein negativer Überlauf dummerweise UB ist. Das sollte man nicht noch provozieren. Letzteres könnte das C++ Gremium irgendwann korrigieren. Das würde ich für sinnvoll halten.
Ich habe jetzt extra nochmal in meine USART Lib geschaut, die auf dem von Peter Fleury basiert, alles was mit Ringbuffer und Indexzugriffen zu tun hat ist unsigned. Alles andere hätte mich auch gewundert.
Schon deshalb, weil (wie geschrieben) die Indizes in C ohnehin signed sind, und du dir damit irgendwelche Artefakte beim Übergang von unsigned auf signed (und vor allem zurück) ersparst.
Im Ausdruck e1[e2] oder e2[e1] kann der eine Ausdruck ein array-designator oder ein Zeiger auf T sein, der jeweils andere Ausdruck muss dann von integralem Typ sein (egal welcher).
Oft wird außer Acht gelassen, dass bei a[i] oder ptr[i], a oder ptr ja kein Zeiger auf das erste Element sein muss. Insofern kann also ptr[-42] durchaus non-UB sein, wenn das Ergebnis *(ptr - 42) die Dereferenzierung einer gültigen Adresse (hier: ein Array-Element) ist.
Da Zeiger ja als Iteratoren aufgefasst werden können, ist es klar, dass ptr1 - ptr2 den Datentyp prtdiff_t haben muss.
Hat man natürlich die Modellvorstellung, dass man Arrays als indizierbare Objekte auffasst, dann machen natürlich nur nicht-negative Indizes Sinn, deren Wertebereich an das Array angepasst ist.
So lange du nicht einmal die Grundlagen von C verinnerlicht hast, musst
du dir darum wirklich keine Gedanken machen.
Sobald hier irgend jemand eine Frage zu C stellt - egal welches Niveau die Frage hat, bzw. auf welches Niveau des Fragenden die gestellte Frage hindeutet - kommen sie wie Ratten aus Ihren Löchern. Die Experten, die Wissenden … die sich dann zuerst damit legitimieren, den Fragesteller als dämliches Arschloch hinzustellen, der vielleicht alles kann - nur kein C!
Ein Index ins komplette Array natürlich nicht. Aber da man bei Arrays
auch oft mit Zeigern hantiert, kann man eben auch sowas wie "ich möchte
ein Objekt von hier aus zurück gucken" damit machen – wobei man
natürlich sicherstellen muss, dass es an der Stelle dann auch überhaupt
etwas gibt.
So sind eben Indizes in C per definitionem vom Typ "int" – und zurück
zur Eingangsfrage, damit wäre "int" auch der logischerweise sinnvollste
Datentyp für ihn. Er ist außerdem normalerweise der Datentyp, der native
"zur Maschine passt" und damit effizient ist (von kleinen Architekturen
wie dem AVR abgesehen, bei denen ein "int" zwei Byte benutzt).
Tut mir leid Jörg, aber wie schon weiter oben geschrieben werden hier 2 Dinge vermischt. Der +/- Sprungwert hat nichts mit dem Index selbst zu tun. Niemals. Die Zeigeradresse als Ziel kann ja auch niemals negativ sein.
Du erwartest zu viel von C. Die Programmiersprache sollte mit der damals verfügbaren Hardware effizient implementierbar sein und prinzipiell auch andere Hardware unterstützen. Schön ist das nicht, war aber auch nie das Ziel.
Tut mir leid Jörg, aber wie schon weiter oben geschrieben werden hier 2
Dinge vermischt.
Die werden allerdings in C eben konsequent vermischt, da man Zeiger und Arrays an vielen Stellen gegeneinander austauschbar benutzen kann. Wenn du einen Ausdruck
1
a[i]
siehst, kannst du ohne Verifikation dessen, wie "a" wirklich definiert worden ist, nicht sagen, ob es sich bei "a" um ein Array oder einen Zeiger handelt.
Wenn "a" ein Array ist, hat logischerweise "a[-1]" keinen Sinn, denn es zeigt vor das Array. Wenn "a" ein Zeiger ist, kann es dagegen durchaus Sinn haben.
Leute, wir reden doch hier im Thread nur von Arrays. Die Frage vom TO dreht sich um Arrays. Auch bei Zeigern sehe ich keine negativen Adressen im Adressbereich. Wie gesagt ich trenne Berechnung und Zugriff.
Hilft aber nichts, wenn die Sprache selbst Zeiger und Arrays oft genug gegenseitig austauschbar macht.
Auch bei Zeigern sehe ich keine
negativen Adressen im Adressbereich.
Darum geht es ja auch nicht, und genau genommen ist über "Adressen" im C-Standard eh nichts geschrieben. Dass "a[-1]" immer auf ein gültiges Element zeigen muss, ist sonnenklar.
a[i] ist doch nur eine alternative Syntax für *(a+i). Wenn man das im Hinterkopf behält, wird der Zusammenhang zwischen Zeiger, Arrays und Indexe plötzlich ganz logisch.
C ist keine richtige Hochsprache, es ist eher eine Niedrigsprache, sehr hardwarenah umgesetzt. Das darf man nie vergessen.
a[i] ist doch nur eine alternative Syntax für *(a+i).
Es ist sogar noch strenger: der subscript-operator [] in der Form e1[e2] ist definiert als *(e1 + e2) (hatte ich ja oben schon geschrieben). Und damit ist eben e1[e2] == *(e1 + e2) == e2[e1].
eigentlich habe ich meine Meinung schon zu oft wiederholt. Nochmal möchte ich das nicht tun. Da hier sowieso schon viel zu sehr Arrays mit Zeigern gemischt wurde wird es langsam mühsam alles aufzudrösseln. Der Thread hat sich zu sehr aufgefächert. Ich werde den Thread im Hinterkopf behalten. Derzeit ist meinerseits alles gesagt. Nimms mir nicht übel Wilhelm.
eigentlich habe ich meine Meinung schon zu oft wiederholt. Nochmal
möchte ich das nicht tun. Da hier sowieso schon viel zu sehr Arrays mit
Zeigern gemischt wurde wird es langsam mühsam alles aufzudrösseln. Der
Thread hat sich zu sehr aufgefächert. Ich werde den Thread im Hinterkopf
behalten. Derzeit ist meinerseits alles gesagt. Nimms mir nicht übel
Wilhelm.
Alles gut: ich denke, wir sind da gar nicht weit auseinander ;-)
M.E. entsteht die Verwirrung durch eine Vermischung von Konzepten, die bei C einfach nicht wirklich auseinander gehalten werden (können).
Der Index selbst bleibt aber positiv. Ich denke hier wird etwas
vermischt was man nicht vermischen sollte.
Mit dem letzten Satz stimme ich dir zu. Man sollte vorzeichenbehaftet
und vorzeichenlos möglichst nicht mischen. Aber ich fand es nie
sonderlich sinnvoll, den Datentyp, insbesondere seine signedness dazu zu
verwenden, den möglichen Wertebereich zu beschränken. Wenn 5 - 6 nicht
-1 ist, sondern 4294967295, macht's das auch nicht besser. Ich habe mir
zur Regel gemacht, dass ich eine Variable nur in zwei Situationen
unsigned mache:
Wenn ich den Wertebereich nach oben brauche
Wenn ich damit Bitgefummel machen will
Für alles andere nehme ich einen vorzeichenbehafteten Typ.
Hallo,
hierauf möchte ich doch noch eingehen. Hatte ich übersehen.
Wir reden ja nachwievor von einem Array Indexzugriff. Wenn dabei eine Rechnung wie a[5-6] zu Stande kommt, dann läuft schon im Vorfeld etwas schief. Und damit kommen wir der Sache schon näher warum ich dafür keinen signed Datentyp haben möchte. signed Überläufe sind UB. Was will man da machen? unsigned Überläufe sind immer korrekt. Das heißt ich muss mich nur um eine mögliche maximal Limit Abfrage kümmern und kann mich darauf verlassen. Deswegen gehe ich dafür anders ran. signed nur wenn ich negative Werte benötige.
Da hier sowieso schon viel zu sehr Arrays mit
Zeigern gemischt wurde
Es ist aber so....
Die Zugriffe über Zeiger, oder über einen ArrayIndex sind äquivalent.
So steht es in jedem C oder C++ Buch.
Wobei beim Zugriff die Array Grenzen beachtet werden müssen, sonst mündet es in einen UB.
Der Zeiger an sich, kann jeden beliebigen Wert annehmen.
Sowohl über den "nutzbaren" Array Bereich/Ende hinaus, wird z.B. bei den Iteratoren genutzt.
Auch NULL oder nullptr kann er werden, was dann auf größeren Kesselchen gerne eine Exception wirft, wenn man ihn nutzt. Oder kann auch wie bei strtok() das Verhalten beeinflussen.
Zu den "beliebigen Werten" gehören auch negative Werte eines Zeigers.
Nochmal ganz klar:
Der Zugriff mit einem "falschen" Zeiger oder Index kann/wird fatal sein.
Aber dem Zeiger macht das nichts....
Der darf "falsch" sein.
Und wie gesagt, in manchen Situationen muss er gar "falsch" werden können. z.B. damit der Range based loop ein Ende findet.
Nochmal ganz klar:
Der Zugriff mit einem "falschen" Zeiger oder Index kann/wird fatal sein.
Aber dem Zeiger macht das nichts....
Der darf "falsch" sein.
Och Leute: ein Zeiger kann irgendwohin zeigen, nur bei der Dereferenzierung kann es zu UB führen. Das hat man regelmäßig bei einer Iteration mit Iteratoren aka Zeigern (jeder Zeiger kann ein Iterator sein): der Zeiger darf eins-hinter-das-letzte-Element zeigen, nur dereferenzieren darf man ihn nicht. Jungs: das ist doch Standard!
Wenn dabei eine Rechnung wie a[5-6] zu Stande kommt, dann läuft schon im
Vorfeld etwas schief. Und damit kommen wir der Sache schon näher warum
ich dafür keinen signed Datentyp haben möchte. signed Überläufe sind UB.
Was will man da machen? unsigned Überläufe sind immer korrekt.
Das hilft dir aber absolut nichts, dass dann in diesem Falle eine Zahl wie 0xffff oder 0xffffffff als Index rauskommt. Der Zugriff, den du darüber anstellst, greift "ins Leere" und ist damit ganz genauso UB.
"5 - 6" ist natürlich kein UB, es ist einfach -1. Nur der Zugriff auf ein Array (nicht notwendigerweise bei einem Zeiger) mit dem Index -1 ist dann wieder UB, genauso, wie es der Zugriff mit 0xffff oder 0xffffffff außerhalb der Feldgrenzen ist.
M.E. entsteht die Verwirrung durch eine Vermischung von Konzepten, die
bei C einfach nicht wirklich auseinander gehalten werden (können).
Es geht noch weiter: In C sind das gar nicht erst unterschiedliche Konzepte, sondern es ist ein und das selbe. Wenn bei a[i] nämlich a ein Array ist, dann wird dieses zunächst für diese Operation in einen Zeiger auf das erste Element konvertiert und dann die Zeiger-Dereferenzierung entsprechend *(a+i) ausgeführt.
Er ist außerdem normalerweise der Datentyp, der native
"zur Maschine passt" und damit effizient ist (von kleinen Architekturen
wie dem AVR abgesehen, bei denen ein "int" zwei Byte benutzt).
… und abgesehen von großen Architekturen, wo die native Breite 64 Bit ist und int nur 32 Bit.
Nochmal ganz klar:
Der Zugriff mit einem "falschen" Zeiger oder Index kann/wird fatal sein.
Aber dem Zeiger macht das nichts....
Der darf "falsch" sein.
Och Leute: ein Zeiger kann irgendwohin zeigen, nur bei der
Dereferenzierung kann es zu UB führen.
Nein. Zeigerarithmetik (auch ohne Dereferenzierung) außerhalb des Bereichs von Anfang des Arrays bis ein Element nach dessen Ende resultiert in undefined behavior.
jetzt verläuft der Thread wieder in die richtige Richtung. Danke Wilhelm.
Die Frage vom TO war nach dem Datentyp der Indexvariablen selbst. Nicht mehr und nicht weniger. Deswegen wiederhole ich mich. size_t und gut ist.
Womit ich nicht einverstanden bin, Onkel Jörg, dass muss ich leider einmal sagen, sind unvollständige Zitate, wenn auf dem weggelassenem Teil rumgeritten wird obwohl es beschrieben ist. Diese Art der Unterhaltung macht keinen Sinn. Ganz ehrlich.
Das Bsp. Ringbuffer macht es deutlich. Programmiert das jemand mit signed für den Indexzähler? Ich glaube nicht. Wenn doch muss er mehr Sicherheiten einbauen.
Die Frage vom TO war nach dem Datentyp der Indexvariablen selbst. Nicht
mehr und nicht weniger. Deswegen wiederhole ich mich. size_t und gut
ist.
size_t ist in der Theorie falsch. Das ist die Grösse eines einzigen zusammenhängenden Objekts im Speicher. Denk mal an die segmentierten Architekturen... zum Glück sind die ausgestorben... oder an embedded Systeme, die ein externes SPI Flash in einen Speicherbereich mappen und dafür spezielle Zeiger verwenden, die grösser als INT und size_t sind... ptrdiff_t kommt da schon eher hin, ich glaube aber, dass man damit einen performance worst-case in Kauf nimmt. Zum Glück ist es in der Praxis meist egal, welchen Type man nimmt, da der Compiler den Ausdruck ptr - index auf den richtigen Typ konvertiert, je nach Zeigertype (siehe etwa FAR, NEAR Zeiger).
Nur wenn man die Differenz ptr - index abspeichern muss, nimmt man ptrdiff_t. Auf jeder 2'er Komplementär CPU ist es zum Glück auch egal ob man INT oder UNSIGNED INT nimmt, das resultierende Bitmuster ist identisch...
Heute braucht man sich um all das keine Gedanken machen, man nimmt einfach irgendeinen 64 bit Type, der jeden Speicher der jemals gebaut worden ist adressieren kann - Es gibt ja auch noch genug echte Probleme zu lösen :-)
size_t ist in der Theorie falsch. Das ist die Grösse eines einzigen
zusammenhängenden Objekts im Speicher. Denk mal an die segmentierten
Architekturen... zum Glück sind die ausgestorben... oder an embedded
Systeme, die ein externes SPI Flash in einen Speicherbereich mappen und
dafür spezielle Zeiger verwenden, die grösser als INT und size_t sind...
In C ist size_t die richtige Wahl für die Indizierung eines Arrays. Weil es eben immer geeignet ist, die Größe eines maximal großen Objektes anzugeben. Und damit eben auch immer für die Indizierung ausreicht.
Und dem C Standard ist es egal, was für SPI-Flash existieren.
Allerdings mag es sein, das size_t nicht die optimale (weil zu große) Wahl für einen Index ist. Denn generell sollte ein DT immer passend gewählt werden. Da es aber keine ganzzahligen DT in C mit einem beliebigen Wertebereich gibt, muss man auf die Krücke zurück kommen, und einen der primitiven Ganzzahltypen zu nehmen, der ausreichend groß ist, aber eben nicht zu groß, und Zusicherungen verwenden, um den Wertebereich wenigstens zur Laufzeit einzugrenzen (besser wäre natürlich zur Compilezeit).
Heute braucht man sich um all das keine Gedanken machen, man nimmt
einfach irgendeinen 64 bit Type, der jeden Speicher der jemals gebaut
worden ist adressieren kann
Früher hat man bei der Frage nach zukünftigen und damit zu adressierenden Speichergrößen in IBM PCs wenigstens versucht, in die Zukunft zu gucken: „640K ought to be enough for anyone“ (1981).
Und damit kommen wir der Sache schon näher warum ich dafür keinen signed
Datentyp haben möchte.
Was du möchtest spielt keine Rolle. Die Sprache wurde vor langer Zeit spezifiziert, und bleibt so bis in alle Ewigkeit. Ansonsten wäre das kein C mehr. Suche dir eine andere Programmiersprache, wenn du damit nicht zurecht kommst.
damit habe ich beim lesen auch so meine Probleme. Der Name ptrdiff_t
sagt etwas aus über Differenzen. Keine Indexe.
Folgendes funktioniert und zeigt wie ein Compiler intern "denkt". Schön ist es nicht und absolut nicht zu empfehlen aber nur soviel zu Differenzen und Indexe.
damit habe ich beim lesen auch so meine Probleme. Der Name ptrdiff_t
sagt etwas aus über Differenzen. Keine Indexe.
Folgendes funktioniert und zeigt wie ein Compiler intern "denkt". Schön
ist es nicht und absolut nicht zu empfehlen aber nur soviel zu
Differenzen und Indexe.
Einfach mal einen Blick in die C-Bibel von Dennis Ritchie und Brian W. Kernighan werfen. Da drin wird dieses Verhalten beschrieben.
Zu arrays vs pointers ein Zitat aus ISO 9899:1999 (6.3.2.1):
Except when it is the operand of the sizeof operator or the unary & operator, or is a string literal used to initialize an array, an expression that has type "array of /type/" is converted to an expression of type "pointer to /type/" that points to the initial element of the array object and is not an lvalue.
Ich denke, es wird hier gründlich aneinander vorbei geredet.
Der TO wollte wissen, welcher DT für die Indizierung eines Arrays mit Hilfe des subscript-operators geeignet ist. Und da ist die Antwort ganz einfach: jeder integrale DT. Er wollte aber auch wissen, welche DT in seinem speziellen Fall der optimale ist aus Sicht der Performance. Da ist die Antwort von der Implementierung abhängig. Antworten wurden schon gegeben.
Etwas anderes ist der Aspekt von Veit D. Hier geht es um die Abstraktion des Konzeptes Feld bzw. Array (manchmal auch Reihung). Das ist üblicherweise eine Aggregation von Elementen. Und es erlaubt unterschiedliche Zugriffsarten auf die Elemente.
Eine sehr häufig und auch von Veit D. zugrunde gelegte Definition eines Feldes ist ein Konzept, welches die Elemente auf die natürlichen Zahlen inkl. 0 abbildet. Insofern ist hier eine Indizierung mit nicht-negativen Indizes bis zur Kardinalität des Feld nahe liegend.
Eine andere Art des Zugriffs ist mit Hilfe eines Iterators. Mit seinem inneren Zustand verweist der Iterator auf ein Element. Eine Dereferenzierung des Iterators liefert das Element. Iteratoren unterstützen üblicherweise Manipulationen wie Inkrement oder auch Addition mit einer Ganzzahl.
Soweit so gut.
Und jetzt kommt C ins Spiel und vermischt einiges. Ich hatte oben schon mehrfach dargelegt, dass e1[e2] == *(e1 + e2) == e2[e1] ist, bzw. der subscript-operator in C tatsächlich so definiert wird. Hierbei dürfen e1 oder e2 jeweils Zeiger oder auch array-Bezeichner sein. Und das ist das Problem.
Handelt es sich um einen Array-Bezeichner, so darf der jeweils andere Ausdruck nur eine nicht-negative Ganzzahl sein eines integralen Typs. Handelt es sich um einen Zeiger, so geht jeder Ganzzahlwert eines integralen Typs. (In beiden Fällen darf es nicht zu einer Bereichsüberschreitung des Element-Bereiches inkl. eins-hinter-dem-letzten kommen. Ich schreibe bewusst Element-Bereich und nicht Speicher!).
Arrays in C haben zusätzlich noch die Eigenschaft, dass die Elemente zusammenhängend sind. Dies ist notwendig, damit Zeiger als Iteratoren genutzt werden können. Und dies bedeutet auch, dass man Zeiger als Iteratoren subtratieren kann und mit Ganzzahlen addieren kann.
Desweitern lässt C einen Array-Bezeichner in fast allen Situationen zu einem Zeiger zerfallen.
Veit D. nimmt sich aus diesem Gemisch in C die sinnvolle Betrachtung heraus, dass ein C-Array dem abstrakten Konzepts eines Feldes mit indizierter Elementadressierung entspricht. Das finde ich persönlich absolut o.k. und ich sehe das genauso. Insofern ist es folgerichtig, dass wir nur vorzeichenlose ganzzahlige DT als sinnvolle Indizes betrachten. Wobei für mich dies immer noch eine Krücke ist, weil es in C nicht möglich ist, den Wertebereich genau auf die Größe des statischen Containers abzustimmen. Also, ein vorzeichenloser ganzzahliger DT mit dem Wertebereich [0, 100[ für ein Array mit 100 Elementen (In C++ etwa als uint<0, 99> dargestellbar).
Hierbei dürfen e1 oder e2 jeweils Zeiger oder auch array-Bezeichner sein.
Wie ich schon sagte: e1 oder e2 muss immer ein Zeiger sein. Arrays werden ggf. vorher implizit in Zeiger auf deren erstes Element konvertiert.
Und das ist das Problem.
Handelt es sich um einen Array-Bezeichner, so darf der jeweils andere
Ausdruck nur eine nicht-negative Ganzzahl sein eines integralen Typs.
Handelt es sich um einen Zeiger, so geht jeder Ganzzahlwert eines
integralen Typs.
Das würde ich anders betrachten: Der Zugriff bei Zeiger-Dereferenzierung darf nur innerhalb der Grenzen des Arrays stattfinden. Daraus ergibt sich implizit, dass der Wert nicht kleiner als 0 sein darf, falls dieser Zeiger auf das erste Element zeigen sollte (und damit auch, wenn man direkt ein Array angibt, das in eben genau so einen Zeiger konvertiert wird). Und wenn der Zeiger auf das zweite Element zeigt, darf der Wert eben nicht kleiner als -1 sein, u.s.w.
Bei dieser Sichtweise ergibt sich dann gar kein Unterschied mehr, ob man ein Array oder einen Zeiger angegeben hat, und genauso ist es in C auch gedacht. Warum sollte man dann unterschiedliche Index-Typen verwenden?
Oder anders betrachtet:
Sagen wir mal, ich habe eine Funktion mit einem lokalen Array, und ich führe verschiedene Operationen auf diesem Array aus. Jetzt beschließe ich, diese in eine Unterfunktion auszulagern und übergebe dieser dafür einen Zeiger auf den Anfang des Arrays. Der Code bleibt abgesehen davon, dass ich die Größe nicht mehr mit sizeof bestimmen kann, genau gleich. Eurer Argumentation nach sollte ich jetzt die Signeness des Indextyp ändern, weil ich nicht mehr mit einem Array, sondern mit einem Zeiger arbeite. Da sehe ich keinen Sinn darin.
Ich lese hier etliche Male etwas von Vorzeichen bei Adressen oder Pointern.
Aber: Adressen und Pointer sind von Natur aus ja gar keine Zahlen, also können sie nicht positiv und auch nicht negativ sein. Sie haben gar kein Vorzeichen, in C nicht und auch in keiner anderen Sprache einschließlich Assembler/Maschinensprache.
Man muss halt gedanklich sauber zwischen Typ und Repräsention unterscheiden.
Angenommen, ein Compiler benutzt bei einem 16-Bit-Rechner beim Typ "bool" für 'true' die Repräsentation 0x0000 und für 'false' die Repräsentation '0xffff', da würde ja auch niemand davon sprechen, dass 'false' negativ ist.
Das Problem bei C ist nur, dass es für Adressen und Pointer keinen explizit benannten Typ gibt, sodass man einen anderen Namen (z. B. 'uint' oder 'int') dafür 'missbrauchen' muss. Das ändert aber nichts daran, dass jede als Adresse oder Pointer genutzte Variable von einem Nicht-Zahlentyp ist. Es wird notfalls halt vom Compiler einfach für den Programmierer unsichtbar gecastet.
In C# hat MS dieses Problem erkannt und gelöst. Beim Übergang von C nach C++ ist es leider mitgeschleppt worden, mit den bekannten Folgen, nämlich den Sicherheits- und Zuverlässigkeitsproblemen durch Buffer-Overflows etc. Gut, damals war das Programmierumfeld noch völlig anders (kein Web, fast keine PCs und somit andere Programmierer und Programmier-Paradigmen, ...).
Hierbei dürfen e1 oder e2 jeweils Zeiger oder auch array-Bezeichner sein.
Wie ich schon sagte: e1 oder e2 muss immer ein Zeiger sein. Arrays
werden ggf. vorher implizit in Zeiger auf deren erstes Element
konvertiert.
Nö.
6.5.2.1 Array subscripting
Semantics
2 A postfix expression followed by an expression in square brackets [] is a subscripted designation of
an element of an array object. The definition of the subscript operator [] is that E1[E2] is identical
to (*((E1)+(E2))). Because of the conversion rules that apply to the binary+ operator, if E1 is an
array object (equivalently, a pointer to the initial element of an array object) and E2 is an integer,
E1[E2] designates the E2 -th element of E1 (counting from zero).
Und das ist das Problem.
Handelt es sich um einen Array-Bezeichner, so darf der jeweils andere
Ausdruck nur eine nicht-negative Ganzzahl sein eines integralen Typs.
Handelt es sich um einen Zeiger, so geht jeder Ganzzahlwert eines
integralen Typs.
Das würde ich anders betrachten: Der Zugriff bei Zeiger-Dereferenzierung
darf nur innerhalb der Grenzen des Arrays stattfinden.
Ja, habe ich so gesagt.
Daraus ergibt
sich implizit, dass der Wert nicht kleiner als 0 sein darf, falls dieser
Zeiger auf das erste Element zeigen sollte (und damit auch, wenn man
direkt ein Array angibt, das in eben genau so einen Zeiger konvertiert
wird). Und wenn der Zeiger auf das zweite Element zeigt, darf der Wert
eben nicht kleiner als -1 sein, u.s.w.
Ja, habe ich so gesagt.
Allerdings ist es so, dass ein array-Bezeichner nicht immer in einen Zeiger konvertiert wird. Du behauptest, es würde immer passieren.
Bei dieser Sichtweise ergibt sich dann gar kein Unterschied mehr, ob man
ein Array oder einen Zeiger angegeben hat, und genauso ist es in C auch
gedacht. Warum sollte man dann unterschiedliche Index-Typen verwenden?
Du hast die C Brille auf. Ok, dann ist das eben alles eins.
Ich habe eine abtraktere Sichtweise: für mich sind eine Array und ein Iterator unterschiedliche Dinge.
Oder anders betrachtet:
Sagen wir mal, ich habe eine Funktion mit einem lokalen Array, und ich
führe verschiedene Operationen auf diesem Array aus. Jetzt beschließe
ich, diese in eine Unterfunktion auszulagern und übergebe dieser dafür
einen Zeiger auf den Anfang des Arrays. Der Code bleibt abgesehen davon,
dass ich die Größe nicht mehr mit sizeof bestimmen kann, genau gleich.
Genau, hier hast Du mal ein Beispiel dafür, wo ein array-Bezeichner nicht zerfällt und anschließend, wo er in einen Zeiger zerfällt.
Eurer Argumentation nach sollte ich jetzt die Signeness des Indextyp
ändern, weil ich nicht mehr mit einem Array, sondern mit einem Zeiger
arbeite. Da sehe ich keinen Sinn darin.
Nein, ganz und gar nicht. Du hast einen Iterator, der immer auf den Anfang eines Arrays zeigt. Hier machen negative Displacements keinen Sinn.
Ich lese hier etliche Male etwas von Vorzeichen bei Adressen oder
Pointern.
Nein, du liest von Vorzeichen bei Offsets. Es soll zwar wohl auch Architekturen mit negativen Adressen geben, aber das ist als eher exotisch anzusehen.
Aber: Adressen und Pointer sind von Natur aus ja gar keine Zahlen, also
können sie nicht positiv und auch nicht negativ sein.
Und trotzdem kann man mit ihnen rechnen und muss das auch, denn sonst gäbe es keine Arrays.
Allerdings ist eine Adresse etwas absolutes, daher ist es nicht sinnvoll, zwei Adressen mit einander zu addieren oder zu multiplizieren. Man kann aber einen Offset (also etwas relatives) dazu addieren. Dann kann dieser Offset aber auch negativ sein.
Angenommen, ein Compiler benutzt bei einem 16-Bit-Rechner beim Typ
"bool" für 'true' die Repräsentation 0x0000 und für 'false' die
Repräsentation '0xffff', da würde ja auch niemand davon sprechen, dass
'false' negativ ist.
Man würde aber auch nicht davon sprechen, dass es positiv ist. Es ist weder signed, noch unsigned, sondern hat schlicht gar nicht erst das Konzept einer Signedness.
Aber wie gesagt: Es geht um Offsets, und die können selbstverständlich auch negativ sein.
Das Problem bei C ist nur, dass es für Adressen und Pointer keinen
explizit benannten Typ gibt, sodass man einen anderen Namen (z. B.
'uint' oder 'int') dafür 'missbrauchen' muss.
Except when it is the operand of the sizeof operator or the unary &
operator, or is a string literal used to initialize an array, an
expression that has type "array of /type/" is converted to an expression
of type "pointer to /type/" that points to the initial element of the
array object and is not an lvalue.
Der Index-Operator ist hier nicht als eine der Ausnahmen gelistet, also gilt das auch für den.
6.5.2.1 Array subscripting
Der wesentliche Teil hier ist:
Because of the conversion rules that apply to the binary+ operator …
Denn der bezieht sich genau auf den obigen Text. Der Rest des Satzes beschreibt nur, was sich daraus ergibt.
if E1 is an array object (equivalently, a pointer to the initial element
of an array object) and E2 is an integer, E1[E2] designates the E2 -th
element of E1 (counting from zero).
Steht ja auch nochmal da: Wenn es ein Array ist oder ein Zeiger auf das erste Element (was in dem Kontext zu 100% äquivalent ist), dann ergibt sich daraus, dass E2 der Index des Elements innerhalb des Arrays ist. Es ändert aber nichts daran, dass zuerst das Array implizit in einen Zeiger konvertiert und die eigentliche Operation dann damit ausgeführt wird.
Es gibt für die Indizierung schlicht keine separaten Regeln für Arrays und für Zeiger.
Und das ist das Problem.
Handelt es sich um einen Array-Bezeichner, so darf der jeweils andere
Ausdruck nur eine nicht-negative Ganzzahl sein eines integralen Typs.
Handelt es sich um einen Zeiger, so geht jeder Ganzzahlwert eines
integralen Typs.
Das würde ich anders betrachten: Der Zugriff bei Zeiger-Dereferenzierung
darf nur innerhalb der Grenzen des Arrays stattfinden.
Ja, habe ich so gesagt.
Es ging mir um die Formulierung.
Daraus ergibt
sich implizit, dass der Wert nicht kleiner als 0 sein darf, falls dieser
Zeiger auf das erste Element zeigen sollte (und damit auch, wenn man
direkt ein Array angibt, das in eben genau so einen Zeiger konvertiert
wird). Und wenn der Zeiger auf das zweite Element zeigt, darf der Wert
eben nicht kleiner als -1 sein, u.s.w.
Ja, habe ich so gesagt.
Nein, du hast es anders gesagt. Deine Aussage war, dass Arrays und Zeiger bezüglich Indizierung unterschiedlich zu betrachten sind. Ich sage, dass sie genau gleich zu betrachten sind. Bezüglich dem technischen Hintergrund, wie es intern funktioniert, sind unsere Aussagen gleich, aber die Schlussfolgerung daraus ist unterschiedlich.
Allerdings ist es so, dass ein array-Bezeichner nicht immer in einen
Zeiger konvertiert wird. Du behauptest, es würde immer passieren.
Nein, ich behaupte, dass das bei der Index-Operation immer passiert. Ich habe sogar ausdrücklich geschrieben, dass sie bei sizeof nicht passiert. Den Rest kann man dem obigen Zitat aus dem Standard entnehmen.
Bei dieser Sichtweise ergibt sich dann gar kein Unterschied mehr, ob man
ein Array oder einen Zeiger angegeben hat, und genauso ist es in C auch
gedacht. Warum sollte man dann unterschiedliche Index-Typen verwenden?
Du hast die C Brille auf. Ok, dann ist das eben alles eins.
In C sind das gar nicht erst unterschiedliche Konzepte, sondern es ist
ein und das selbe.
Und genau auf der C-Sicht basierend argumentiere ich hier, da wir ja speziell über C sprechen.
Ich habe eine abtraktere Sichtweise: für mich sind eine Array und ein
Iterator unterschiedliche Dinge.
Ok. Die Frage ist, ob diese Trennung hier sinnvoll ist, eben weil C diese Dinge im Prinzip untrennbar vermischt.
Genau, hier hast Du mal ein Beispiel dafür, wo ein array-Bezeichner
nicht zerfällt und anschließend, wo er in einen Zeiger zerfällt.
Natürlich zerfällt er bei jeder Index-Operation in einen Zeiger. Nur beim sizeof eben nicht.
Eurer Argumentation nach sollte ich jetzt die Signeness des Indextyp
ändern, weil ich nicht mehr mit einem Array, sondern mit einem Zeiger
arbeite. Da sehe ich keinen Sinn darin.
Nein, ganz und gar nicht. Du hast einen Iterator, der immer auf den
Anfang eines Arrays zeigt. Hier machen negative Displacements keinen
Sinn.
Von einer Unterscheidung zwischen einem Iterator, der auf das erste Element zeigt und einem, der das nicht tut, habe ich nichts gelesen, nur von der Unterscheidung zwischen Array und Iterator.
Handelt es sich um einen Array-Bezeichner, so darf der jeweils andere
Ausdruck nur eine nicht-negative Ganzzahl sein eines integralen Typs.
Handelt es sich um einen Zeiger, so geht jeder Ganzzahlwert eines
integralen Typs. (In beiden Fällen darf es nicht zu einer
Bereichsüberschreitung des Element-Bereiches inkl.
eins-hinter-dem-letzten kommen. Ich schreibe bewusst Element-Bereich und
nicht Speicher!).
...
Desweitern lässt C einen Array-Bezeichner in fast allen Situationen zu
einem Zeiger zerfallen.
Genau wegen dieses Zerfalls ist die Forderung
Handelt es sich um einen Array-Bezeichner, so darf der jeweils andere
Ausdruck nur eine nicht-negative Ganzzahl sein eines integralen Typs.
überflüssig, da sie bereits in dieser enthalten ist:
In beiden Fällen darf es nicht zu einer Bereichsüberschreitung […]
kommen
Auch beim Elementzugriff per Index zerfällt der Array-Bezeichner in
einen Pointer. Der []-Operator deswegen hat immer einen Pointer- und
einen Integer-, aber niemals einen Array-Operanden.
Im Ausdruck p[i] (p ist dabei der Pointer-Operand) zeigt ein gültiges p
immer auf ein Element eines Arrays a oder auf eins danach. Es ist also
p=a+k mit 0≤k≤n, wobei n die Anzahl der Elemente von a ist. Der
Wertebereich des Index i ist somit [-k,n-k-1] und umfasst i.Allg. auch
negative Zahlen. Die einzige Ausnahme stellt der Fall k=0 dar, der bspw.
dann eintritt, wenn p ein zu einem Pointer zerfallener Array-Bezeichner
ist. Nur in diesem speziellen Fall kann für i ein unsigned-Typ verwendet
werden, in allen anderen Fällen muss i signed sein, um den gesamten
Wertebereich abzudecken.
Deswegen gebe ich als Indextyp dem vorzeichenbehafteten ptrdiff_t den
Vorzug gegenüber dem vorzeichenlosen size_t.
Im C-Standard ist zwar so etwas wie ein "nativer" Indextyp nicht
spezifiziert, aber im Abschnitt "Built-in operators" des C++-Standard
ist zu lesen:
1
For every cv-qualified or cv-unqualified object type T there exist
2
candidate operator functions of the form
3
4
T* operator+(T *, std::ptrdiff_t);
5
T& operator[](T *, std::ptrdiff_t);
6
T* operator-(T *, std::ptrdiff_t);
7
T* operator+(std::ptrdiff_t, T *);
8
T& operator[](std::ptrdiff_t, T *);
Das deutet darauf hin, dass auch im C++-Komitee ptrdiff_t als der
sinnvollste Indextyp angesehen wird.
Ungeachtet dessen kann es bei kleinen Array natürlich sinnvoll sein, aus
Effizienzgründen für Indizes einen kleineren Datentyp zu verwenden, der
dann durchaus auch unsigned sein kann. So ist für ein Array mit 256
Elementen uint8_t eine gute Wahl. Dieser Typ ist dann aber fest an die
konkrete Array-Größe gebunden und muss bei einer Änderung derselben ggf.
ebenfalls geändert werden.
Steht ja auch nochmal da: Wenn es ein Array ist oder ein Zeiger auf das
erste Element (was in dem Kontext zu 100% äquivalent ist), dann ergibt
sich daraus, dass E2 der Index des Elements innerhalb des Arrays ist. Es
ändert aber nichts daran, dass zuerst das Array implizit in einen Zeiger
konvertiert und die eigentliche Operation dann damit ausgeführt wird.
Das steht da zwar nicht, aber Zino hat ja die Stelle zitiert, wo explizit drin steht, dass nur bei sizeof der array-Bezeichner nicht zerfällt. Danke! Da war ich mehr von C++ ausgegangen.
Ist aber auch egal an dieser Stelle. Denn ich hatte ja schon gesagt, dass C das zusammen rührt.
Es bleibt der konzeptionelle Unterschied: wenn ich von einem Konzept spreche, hat das ja erstmal gar nichts mit C zu tun. Und das vermischt Du sofort wieder, indem Du wieder nur mit C argumentierst.
Ist mir aber auch egal. Für mich und viele andere gibt es da einen Unterschied und die Sprache C verwischt diesen Unterschied. Du möchtest diesen Unterschied nicht sehen, ist mir auch egal.
Das steht da zwar nicht, aber Zino hat ja die Stelle zitiert, wo
explizit drin steht, dass nur bei sizeof der array-Bezeichner nicht
zerfällt. Danke! Da war ich mehr von C++ ausgegangen.
Bei Array-Elementzugriffen mittels a[i], um die es in diesem Thread ja
primär geht, wird auch in C++ a in einen Pointer konvertiert, sonst wäre
der Built-In-[]-Operator darauf nicht anwendbar.
Im Ausdruck p[i] (p ist dabei der Pointer-Operand) zeigt ein gültiges p
immer auf ein Element eines Arrays a oder auf eins danach. Es ist also
p=a+k mit 0≤k≤n, wobei n die Anzahl der Elemente von a ist. Der
Wertebereich des Index i ist somit [-k,n-k-1] und umfasst i.Allg. auch
negative Zahlen. Die einzige Ausnahme stellt der Fall k=0 dar, der bspw.
dann eintritt, wenn p ein zu einem Pointer zerfallener Array-Bezeichner
ist. Nur in diesem speziellen Fall kann für i ein unsigned-Typ verwendet
werden, in allen anderen Fällen muss i signed sein, um den gesamten
Wertebereich abzudecken.
Hallo,
das musste bitte nochmal erklären. Ich habe das mehrfach gelesen und sehe immer wieder einen Widerspruch.
Du schreibst:
p ... Zeiger
a ... Array
n ... Anzahl der Elemente von a
k ... ???
p=a+k mit 0≤k≤n
Also k und n sind größer gleich Null. Demnach unsigned. ;-)
Im nächsten Satz steht
"Wertebereich des Index i ist somit [-k,n-k-1]"
Woher kommen plötzlich die negativen Werte wenn vorher alles größer 0 definiert wurde? i darf sich nur zwischen 0 und n-1 bewegen.
Ich sage mal so. In dem Thread hat jeder seinen Standpunkt dargelegt und begründet. Man wird ja durch solche Unterhaltungen nicht dümmer.
Vielen Dank dafür.
Das steht da zwar nicht, aber Zino hat ja die Stelle zitiert, wo
explizit drin steht, dass nur bei sizeof der array-Bezeichner nicht
zerfällt. Danke! Da war ich mehr von C++ ausgegangen.
Bei Array-Elementzugriffen mittels a[i], um die es in diesem Thread ja
primär geht, wird auch in C++ a in einen Pointer konvertiert, sonst wäre
der Built-In-[]-Operator darauf nicht anwendbar.
Einserseits hast Du Recht, weil die Liste der overloads der built-in Operatoren nur T* und nicht T oder T& enthält. Andereseits ist sonst überall extra erwähnt, wo eine array-to-pointer conversion stattfindet.
Das deutet darauf hin, dass auch im C++-Komitee ptrdiff_t als der
sinnvollste Indextyp angesehen wird.
Ungeachtet dessen kann es bei kleinen Array natürlich sinnvoll sein, aus
Effizienzgründen für Indizes einen kleineren Datentyp zu verwenden, der
dann durchaus auch unsigned sein kann. So ist für ein Array mit 256
Elementen uint8_t eine gute Wahl. Dieser Typ ist dann aber fest an die
konkrete Array-Größe gebunden und muss bei einer Änderung derselben ggf.
ebenfalls geändert werden.
Also irgendwie macht diese Erklärungen ja nun gar keinen richtigen Sinn. Es geht die ganze Zeit darum ob der Indexzähler signed oder unsigned sein sollte. Das ist völlig unabhängig von dessen Größe. Und auf einmal wird sich auf die Größe bezogen und entweder signed oder unsigned bevorzugt. Sorry, aber das ist jetzt komisch. Entweder es wird signed benötigt oder nicht.
Ich ziehe das nochmal von einer anderen Seite auf. Sagen wir einmal der Einfachheit halber der verfügbare Wertebereich ist maximal 16Bit. Alles Größere wäre nur äquivalent in der Betrachtung. Das heißt man hat int16_t und uint16_t zur Verfügung. Jetzt benötigt man ein Array mit 50.000 Elementen. Demnach nimmt man uint16_t. Der unsigned Datentyp stellt immer den größten nutzbaren Wertebereich zur Verfügung. Allein schon von der Betrachtung macht signed keinen Sinn, auch wenn man diesen für kleinere Wertebereich verwenden könnte.
Man wird ja durch solche Unterhaltungen nicht dümmer.
Schlauer aber auch nicht - jedenfalls nicht als Anfänger.
Eine Sprache (egal ob Programmiersprache oder natürliche Sprache) lernt man indem sie benutzt.
Dazu gehört m.M.n. auch, den Code von erfahreneren Programmierern zu lesen und daraus zu lernen. Das ist das Äquivalent zur Unterhaltung in einer fremden Sprache mit einem dessen Muttersprache das ist.
Das bedeutet auch mal in den Code der genutzten Librarys zu schauen, versuchen zu verstehen, was da passiert, wie das funktioniert, und was der Programmierer sich dabei gedacht hat.
Der Versuch, das aus Lehrbüchern allein zu erlernen kann nur scheitern.
Die sind zwar äußerst hilfreich zum Nachlesen, aber auch erst dann, wenn man erkannt hat, womit man ein Problem hat.
Am Anfang Fehler zu machen gehört dazu. Das ist Teil des Lernprozess, und den hat auch jeder erfahrene Programmierer irgendwann mal durchlaufen.
Solche rein akademischen Diskussionen wie diese hier tragen bei Anfängern eher zur Verwirrung bei und helfen rein gar nichts.
Als Programmierer mit Erfahrung entwickelt man mit der Zeit ohnehin seinen eigenen Stil, wie man an diesem Thread sehr schön ablesen kann.
Bsp.: Der Eine nutzt bevorzugt signed, der Andere unsigned, und beide Begründungen dafür sind durchaus schlüssig...
das musste bitte nochmal erklären. Ich habe das mehrfach gelesen und
sehe immer wieder einen Widerspruch.
Du schreibst:
p ... Zeiger
a ... Array
n ... Anzahl der Elemente von a
k ... ???
k ist der Offset von p bzgl. des Array-Anfangs von a. Vielleicht wird
das Ganze anhand eines Beispiels mit genau diesen Variablen etwas
klarer:
1
#include<stdio.h>
2
#include<stddef.h>
3
4
// Tabelle mit den Quadraten von -4 bis +4
5
inta[]={16,9,4,1,0,1,4,9,16};
6
7
// Aanzahl der Elemente von a
8
#define n (sizeof a / sizeof a[0])
9
10
// Index des mittleren Elements von a (die 0)
11
#define k (n / 2)
12
13
// Zeiger in die Mitte von a
14
int*p=a+k;
15
16
// p kann nun wie ein Array mit dem Indexbereich [-4,+4] verwendet werden
17
18
intmain(void){
19
for(ptrdiff_ti=-4;i<=+4;i++)
20
printf("p[%2td] = %2d\n",i,p[i]);
21
}
ptrdiff_t als Indextyp ist im konkreten Fall natürlich unnötig groß
(int8_t wäre schon mehr als ausreichend), aber das Array könnte ja in
einer zukünftigen Version sehr viel größer werden. Wichtig ist auf jeden
Fall, dass i ein signed Typ ist, weil es auch negative Werte annehmen
kann.
...
Bei Array-Elementzugriffen mittels a[i], um die es in diesem Thread ja
primär geht, wird auch in C++ a in einen Pointer konvertiert, sonst wäre
der Built-In-[]-Operator darauf nicht anwendbar.
Einserseits hast Du Recht, weil die Liste der overloads der built-in
Operatoren nur T* und nicht T oder T& enthält. Andereseits ist sonst
überall extra erwähnt, wo eine array-to-pointer conversion stattfindet.
Es wird nur dort erwähnt, wo es nicht schon anderweitig aus dem Text
hervorgeht.
Beim Subscripting ist eine gesonderte Erwähnung nicht erforderlich,
nicht nur wegen der candidate operator functions für [], die einen
Pointer als Argument erwarten, sondern auch deswegen:
1
7.6.1.1 Subscripting
2
[…]
3
One of the expressions shall be a glvalue of type “array of T” or a
4
prvalue of type “pointer to T”
Damit ist schon einmal geklärt, dass Subscripting auf Arrays anwendbar
ist.
1
[…]
2
The expression E1[E2] is identical (by definition) to *((E1)+(E2)) […]
Hier kommt eine Addition ins Spiel, die Built-In-Addition ist aber nicht
für Arrays definiert, denn:
1
For addition, either both operands shall have arithmetic or unscoped
2
enumeration type, or one operand shall be a pointer to a
3
completely-defined object type and the other shall have integral or
4
unscoped enumeration type.
Das Array muss also, um als Operand für die Addition akzeptiert zu
werden, erst in etwas Passendes konvertiert werden. Eine direkte
Konvertierung in einen arithmetic oder unscoped enumeration type ist
nicht definiert, also bleibt nur die Konvertierung in einen Pointer, und
die ist tatsächlich möglich:
1
7.3.2 Array-to-pointer conversion
2
3
An lvalue or rvalue of type “array of N T” or “array of unknown bound of
4
T” can be converted to a prvalue of type “pointer to T”.
5
[…]
6
The result is a pointer to the first element of the array.
Somit zerfällt ein Array beim Subscripting auch in C++ zu einem Pointer.
Im Vergleich zu C muss man aber sehr viel mehr Text im Standard lesen,
um zu dieser Erkenntnis zu gelangen.
okay, jetzt weiß ich wovon die Rede war bzw. Anwendung und nun weiß ich warum man aneinander vorbeigeredet hat, jeder hat unterschiedliche Sichtweisen auf den Index vor Augen. Ihr seit mit dem Blickwinkel "Zeiger" immer schon einen Schritt weiter gewesen. Nämlich was mit dem Indexzähler passiert.
Die Einen (ich) sehen in deinem Bsp. den Wertebereich vom Indexzähler von a zwischen 0 und 8. Andere sehen das nicht bzw. schauen anders drauf und hantieren mit Zeigern +/-. Dieser ist praktisch losgelöst vom Array. Ist ja nur ein Zeiger der auf irgendwas zeigt und für das Array verwendet wird.
Ich habe das vor Augen gehabt
1
for(uint8_ti=0;i<n;i++){
2
cout<<a[i]<<endl;
3
}
4
5
oder
6
7
for(auto&d:a){
8
cout<<d<<endl;
9
}
Aber gut, jetzt weiß ich was ihr mit ptrdiff_t so macht. :-) Doch noch was gelernt. Geballtes Forenwissen verständlich gemacht ist schon etwas Feines. Danke an alle bzw. sollte das der TO schreiben. ;-)
Ich kenne sogar eine praktische Anwendung von negativen Indices. In UNIX System V R3 (Ende der 1980er) wurden die ctype-Funktionen isalpha(), isupper(), islower(), isalpha(), isprint() usw. als Macros definiert, nämlich durch einen Zugriff auf ein Array mit 257 Elementen - vermutlich aus Effizienzgründen.
Beispiel aus ctype.h (aus dem Kopf, nicht exakt kopiert):
1
#define isupper(x) (ctype[x] & _ISUPPER)
In der stdio-Lib existieren jedoch nicht nur die Zeichen mit dem Wert von 0 bis 255, sondern auch noch EOF, welches im allgmeinen als (-1) definiert wurde. Somit haben wir insgesamt 257 "Zeichen".
Damit isupper(EOF) (identisch mit isupper(-1)!) nicht undefiniertes Verhalten heraufbeschwor, griff man zu einem Trick: ctype wurde als Pointer auf das eigentliche Array mit dem Offset +1 definiert. Somit war der Zugriff auf
1
ctype[EOF]
bzw.
1
ctype[-1]
vollkommen unproblematisch.
P.S.
Ich vermute, dass auch bereits vor SVR3 diese Mimik für die ctype-Lib eingesetzt wurde. Ich kenne jedoch nur den Source von SVR3, da wir damals eine Source-Lizenz hatten. Heutzutage, wo C-Funktionen vom Compiler einfach so "geinlined" werden können, sind solche wirklich "schmutzigen" Makros obsolet.
P.P.S.
Zur ursprünglichen Frage des TOs: 8-Bit-Variablen sind auf den STM32 tatsächlich erheblich langsamer als 32-Bit-Variablen - nicht nur als Indices. Wenn der TO tatsächlich 8-Bit verwenden will, um anzudeuten, dass sein Index niemals größer als 127 bzw. 255 werden kann, sollte er aus Effizienzgründen int_fast8_t bzw. uint_fast8_t verwenden.
Falks Argument "Der Compiler/Optimierer wirds schon richten" zieht hier nicht: Es ist semantisch etwas ganz anderes, ob ich mit einer 8-Bit-Variablen oder mit einer 32-Bit-Variablen ein Array "durchforste": Erstere wird sich wegen dem automatischen Überlauf 255 -> 0 immer auf eines der ersten 256 Array-Elemente beziehen, letztere läuft erheblich weiter - mit allen Vor- und Nachteilen.
Der Compiler darf dieses automatische 8-Bit-Modulo nicht optimieren, sondern muss tatsächlich durch Maskierung dafür sorgen, dass die 8-Bit-Variable in ihrem beschränkten Wertebereich bleibt. Damit wird ein STM32 erheblich ausgebremst. Ich hatte dazu schon mal vor ein paar Jahren einige Benchmarks in diesem Forum veröffentlicht, welche das auch verdeutlichten.
P.P.P.S
Ich habe mir gerade mal die Datei ctype.h aus den OpenBSD-Sources angeschaut.
Hier wird der Spezialfall EOF etwas anders abgefackelt, indem dieser spziell ohne Array-Zugriff behandelt wird. Trotzdem wird hier auf das Array immer noch mit dem Offset 1 zugegriffen - warum auch immer. Vielleicht aus Kompatibilitätsgründen zu älteren Versionen.
Und weil das ganze kommutativ ist (s.a. oben: e1[e2] == e2[e1] == *(e1 +
e2) ),
geht dann auch dies (stand aber auch schon oben):
printf("p[%d] = %2d\n", -1, (-1)[p]);
Diese unglückliche Eigenschaft der []-Operators wurde jetzt schon schon
alleine in diesem Thread 6-mal erwähnt, davon 3-mal von dir.
Wenn das noch 10-mal wiederholt wird, wird diese umgekehrte Schreibweise
sicher noch zum Standard bei den jungen Programmierern ;-)
Hier sieht man was der MS Compiler draus macht, Windows 10 64-bit.
Wenn der Array Index nicht 64 bit ist, wird er erst mal auf 64 bit erweitert. Die Funktionsparameter werden in den Registern rcx=array und rdx=index übergeben.
Ob man int64_t oder uint64_t verwendet, spielt keine Rolle, es wird identischer Assembler Code erzeugt.