Hallo,
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.
Martin M. schrieb:> Kann man prinzipiell als "Feld" eine 32-Bit Variable verwenden, auch> wenn die Anzahl der Felder nur 100 ist
Ja.
Martin M. schrieb:> 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.
Martin M. schrieb:> 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.
Falk B. schrieb:> 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.
Martin M. schrieb:> 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.
Martin M. schrieb:> Hallo,>> 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.
Rudi schrieb:> By the way: Test und test passen nicht zusammen, das sind 2 verschiedene> Variablen.
Stimmt, habe ich in der Hektik beim Reintippen nicht bemerkt.
Martin M. schrieb:> 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.Falk B. schrieb:> 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.
Veit D. schrieb:> ganz simpel, verwende>> size_t
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.
Martin M. schrieb:> Danke für die Antwort.> Aber generell (abgesehen von Array-Indexen) ist die Verwendung von> Variablen in Bitbreitengröße des µC sinnvoll?
Nur dann, wenn man WIRKLICH optimale, auf die CPU zugeschnittene
Leistung braucht.
AVR-GCC-Codeoptimierung
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.
Martin M. schrieb:>> By the way: Test und test passen nicht zusammen, das sind 2 verschiedene>> Variablen.>> Stimmt, habe ich in der Hektik beim Reintippen nicht bemerkt.
Sowas macht man nicht! Man kopiert IMMMER den originalen Quelltext oder
hängt ihn gleich als Anhang an!
Jörg W. schrieb:> Veit D. schrieb:>> ganz simpel, verwende>>>> size_t>> 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.
Veit D. schrieb:> 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.
In der Tat.
Jörg W. schrieb:> 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,
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. ;-)
Yalu X. schrieb:> Jörg W. schrieb:>> 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.
Veit D. schrieb:> damit habe ich beim lesen auch so meine Probleme. Der Name ptrdiff_t> sagt etwas aus über Differenzen. Keine Indexe.
a[i] kann man auch schreiben als
1
a[ &a[i] - &a[0] ]
2
\_____ _____/
3
V
4
(ptrdiff_t)i
Warum also nicht gleich ptrdiff_t als Typ für i nehmen?
Veit D. schrieb:> 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.
Veit D. schrieb:> Wie kann ein Index negativ sein?
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).
Hallo,
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.
Veit D. schrieb:> also warum signed?
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.
Veit D. schrieb:> char readArray (size_t index)> {> }
Insofern ist das schon mal nicht schlecht, aber
1
AelementOfArray(unit8_ti){
2
assert(i<Size);
3
...
4
}
natürlich besser. Leider können wir ja in C (noch) nicht derart
angepasste DT wie ein uint[0,42] definieren.
Harry L. schrieb:>> 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!
Armselige Gestalten!
Veit D. schrieb:> Gedanklich passt das nicht zusammen. Und was schon gedanklich nicht> zusammenpasst sollte man auch nicht Programmieren. ;-)
S.a. mein Beitrag oben
Jörg W. schrieb:> Veit D. schrieb:>> Wie kann ein Index negativ sein?>> 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.
Veit D. schrieb:> Gedanklich passt das nicht zusammen.
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.
Veit D. schrieb:> 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.
Hallo,
eher nicht die Erwartung viel mehr der Umgang mit den Möglichkeiten.
Auch bei Wilhelm sehe ich diesmal Widersprüche für mich. Dein Bsp. mit
> Da Zeiger ja als Iteratoren aufgefasst werden können, ist es klar, dass> ptr1 - ptr2 den Datentyp prtdiff_t haben muss.
Bis hierher okay. Nur das Ergebnis der Rechnung ist noch nicht der
endgültige Wert für den Indexzugriff.
Hallo,
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.
Veit D. schrieb:> Die Frage vom TO dreht sich um Arrays.
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.
Stefan F. schrieb:> a[i] ist doch nur eine alternative Syntax für *(a+i).
Und da es so ist und da die Addition kommutativ ist, kann man es sogar
(so idiotisch das aussieht) als "i[a]" schreiben. ;-)
Stefan F. schrieb:> 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].
Hallo,
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.
Veit D. schrieb:> Hallo,>> 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).
Wilhelm M. schrieb:> M.E. entsteht die Verwirrung durch eine Vermischung von Konzepten, die> bei C einfach nicht wirklich auseinander gehalten werden (können).
Sehe ich genauso.
Rolf M. schrieb:> Veit D. schrieb:>> 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.
Veit D. schrieb:> 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.
Veit D. schrieb:> Wenn dabei eine> Rechnung wie a[5-6] zu Stande kommt, dann läuft schon im Vorfeld etwas> schief.
Absolut: unter der Prämisse, dass a ein Array-designator ist und kein
Zeiger, der in die Mitte eines Arrays zeigt.
Veit D. schrieb:> Das heißt ich muss> mich nur um eine mögliche maximal Limit Abfrage kümmern und kann mich> darauf verlassen.
Dazu hatte ich oben ein Beispiel mit Zusicherungen. Der nicht-negative
DT spart eine Zusicherung ;-)
Arduino F. schrieb:> 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!
Veit D. schrieb:> 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.
Wilhelm M. schrieb:> 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.
Jörg W. schrieb:> 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.
Wilhelm M. schrieb:> Arduino F. schrieb:>> 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.
Hallo,
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.
Gute Nacht.
Veit D. schrieb:> 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 :-)
Udo K. schrieb:> 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).
Udo K. schrieb:> 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).
Veit D. schrieb:> 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.
Veit D. schrieb:> 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.
Michael D. schrieb:> Veit D. schrieb:>> 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).
Wilhelm M. schrieb:> 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, ...).
Rolf M. schrieb:> Wilhelm M. schrieb:>> 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.
Rolf schrieb:> 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.
Hä? Natürlich gibt es Pointertypen.
Wilhelm M. schrieb:>> 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ö.
Doch. Oben wurde ja schon der Teil zitiert:
Zino schrieb:> 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.
Hab ich ja oben auch ausdrücklich geschrieben:
Rolf M. schrieb:> 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.
Anmerkung vorweg: Einiges in diesem Beitrag deckt sich mit dem, was Rolf
M. schon geschrieben hat und dem ich uneingeschränkt zustimme.
Wilhelm M. schrieb:> 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.
Rolf M. schrieb:> 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.
Wilhelm M. schrieb:> 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.
Yalu X. schrieb:> 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.
Yalu X. schrieb:> Wilhelm M. schrieb:>> 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.
Yalu X. schrieb:> 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.
Veit D. schrieb:> 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...
just my 2cent
Veit D. schrieb:> 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.
Wilhelm M. schrieb:> Yalu X. schrieb:>> ...>> 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.
Hallo,
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. ;-)
Yalu X. schrieb:> 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:
Und weil das ganze kommutativ ist (s.a. oben: e1[e2] == e2[e1] == *(e1 +
e2) ),
geht dann auch dies (stand aber auch schon oben):
Yalu X. schrieb:> Das Array muss also, um als Operand für die Addition akzeptiert zu> werden, erst in etwas Passendes konvertiert werden.
Ok, das hatte ich übersehen! Danke!
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.
Link: https://github.com/openbsd/src/blob/master/include/ctype.h
Auszug:
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.
Wilhelm M. schrieb:> 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 ;-)
Yalu X. schrieb:> Diese unglückliche Eigenschaft der []-Operators wurde jetzt schon schon> alleine in diesem Thread 6-mal erwähnt, davon 3-mal von dir.
Nun, ich habe nur den C-Standard zitiert. Oder möchtest Du das lieber
unter den Teppich kehren?
Yalu X. schrieb:> Wenn das noch 10-mal wiederholt wird, wird diese umgekehrte Schreibweise> sicher noch zum Standard bei den jungen Programmierern ;-)
Warum nicht? Auch daran wird man sich gewöhnen ...
Yalu X. schrieb:> Wenn das noch 10-mal wiederholt wird, wird diese umgekehrte Schreibweise> sicher noch zum Standard bei den jungen Programmierern ;-)
Weil es auch von KI Tools übernommen wird.
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.
Udo K. schrieb:> Ob man int64_t oder uint64_t verwendet, spielt keine Rolle, es wird> identischer Assembler Code erzeugt.
Was zu erwarten war bzw. auch nicht in Frage stand.