Index von Array: welcher Datentyp in C

OP #7454971
Lesenswert?

Hallo,

ich habe mal eine Frage zum Dateityp, den man als Index verwendet, wie z.B.

1
uint16_t array[100];    //Array mit 100 Elementen
2
uint16_t Test;          //Testvariable
3
uint8_t Feld;           // 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_t array[100];    //Array mit 100 Elementen
2
uint16_t Test;          //Testvariable
3
uint32_t Feld;          // 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.

#7454973
Lesenswert?

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.

OP #7454976
Lesenswert?

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.

#7454984
Lesenswert?

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.

Gast #7455000
Lesenswert?

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.

Moderator Persönliche Seite #7455018
Lesenswert?

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.

Gast #7455021
Lesenswert?

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.

#7455032
Lesenswert?

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.

#7455052
Lesenswert?

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.

#7455056
Lesenswert?

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.

Moderator #7455058
Lesenswert?

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.

#7455084
Lesenswert?

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
char readArray (size_t index)
2
{
3
}

Wie ich den Index vorher berechne steht auf einem anderen Blatt. Sind eben so meine Überlegungen dafür. Tut mir leid. ;-)

#7455088
Lesenswert?

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.

#7455121
Lesenswert?

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.

Moderator Persönliche Seite #7455129
Lesenswert?

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).

#7455130
Lesenswert?

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.

#7455132
Lesenswert?

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
A elementOfArray(unit8_t i) {
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.

#7455133
Lesenswert?

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!

#7455136
Lesenswert?

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.

Gast #7455137
Lesenswert?

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.

Moderator Persönliche Seite #7455141
Lesenswert?

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.

#7455144
Lesenswert?

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.

Moderator Persönliche Seite #7455146
Lesenswert?

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.

Gast #7455148
Lesenswert?

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.

#7455211
Lesenswert?

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.

#7455213
Lesenswert?

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).

#7455223
Lesenswert?

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.

(Firma: Gast) #7455229
Lesenswert?

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.

#7455230
Lesenswert?

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 ;-)

#7455231
Lesenswert?

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!

Moderator Persönliche Seite #7455234
Lesenswert?

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.

#7455242
Lesenswert?

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.

#7455255
Lesenswert?

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.

Gast #7455267
Lesenswert?

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 :-)

#7455270
Lesenswert?

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).

#7455272
Lesenswert?

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).

Gast #7455281
Lesenswert?

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.

#7455298
Lesenswert?

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.

1
// ptrtest.c
2
#include <stdio.h>
3

4
int main() {
5
int a[] = {123,456,789};
6
int index = 2;
7
int *pint = &a[0];
8

9
int w = a[index];
10
int x = index[a];
11
int y = *(a + index);
12
int z = *(index + pint);
13

14
printf("%d %d %d %d\n", w, x, y, z);
15

16
return 0;
17
}

gcc -Wall -o ptrtest ptrtest.c ./ptrtest

789 789 789 789

Michael

#7455301
Lesenswert?

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.

#7455335
Lesenswert?

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.

#7455337
Lesenswert?

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).

#7455346
Lesenswert?

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.

#7455352
Lesenswert?

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, ...).

#7455355
Lesenswert?

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.

#7455385
Lesenswert?

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.

Moderator #7455394
Lesenswert?

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.

#7455409
Lesenswert?

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.

Moderator #7455420
Lesenswert?

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.

#7455489
Lesenswert?

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.

#7455493
Lesenswert?

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.

#7455494
Lesenswert?

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.

#7455500
Lesenswert?

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

Moderator #7455517
Lesenswert?

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
int a[] = { 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
int main(void) {
19
  for (ptrdiff_t i=-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.

Moderator #7455541
Lesenswert?

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.

#7455564
Lesenswert?

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_t i=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. ;-)

Moderator Persönliche Seite #7455611
Lesenswert?

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:

1
#if !defined(_ANSI_LIBRARY) && !defined(__cplusplus)
2

3
__only_inline int isalnum(int _c)
4
{
5
  return (_c == -1 ? 0 : ((_ctype_ + 1)[(unsigned char)_c] & (_U|_L|_N)));
6
}

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.

Moderator #7455646
Lesenswert?

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 ;-)

#7455765
Lesenswert?

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 ...

Gast #7455919
Lesenswert?

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.

1
#include <stdint.h>
2

3
int foo_char(int array[], char index)     { return array[index]; }
4
int foo_i32(int array[], int index)       { return array[index]; }
5
int foo_u32(int array[], unsigned index)  { return array[index]; }
6
int foo_i64(int array[], int64_t index)   { return array[index]; }
7
int foo_u64(int array[], uint64_t index)  { return array[index]; }
8
int foo_size_t(int array[], size_t index) { return array[index]; }
1
foo_char:
2
  0000000000000000: 48 0F BE C2        movsx       rax,dl
3
  0000000000000004: 8B 04 81           mov         eax,dword ptr [rcx+rax*4]
4
  0000000000000007: C3                 ret
5

6
foo_i32:
7
  0000000000000000: 48 63 C2           movsxd      rax,edx
8
  0000000000000003: 8B 04 81           mov         eax,dword ptr [rcx+rax*4]
9
  0000000000000006: C3                 ret
10

11
foo_u32:
12
  0000000000000000: 8B C2              mov         eax,edx
13
  0000000000000002: 8B 04 81           mov         eax,dword ptr [rcx+rax*4]
14
  0000000000000005: C3                 ret
15

16
foo_i64:
17
  0000000000000000: 8B 04 91           mov         eax,dword ptr [rcx+rdx*4]
18
  0000000000000003: C3                 ret
19

20
foo_u64:
21
  0000000000000000: 8B 04 91           mov         eax,dword ptr [rcx+rdx*4]
22
  0000000000000003: C3                 ret
23

24
foo_size_t:
25
  0000000000000000: 8B 04 91           mov         eax,dword ptr [rcx+rdx*4]
26
  0000000000000003: C3                 ret

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren