Forum: Mikrocontroller und Digitale Elektronik Index von Array: welcher Datentyp in C


von Martin M. (martin69)


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.

von Harry L. (mysth)


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.

von Falk B. (falk)


Lesenswert?

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.

von Martin M. (martin69)


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.

von Harry L. (mysth)


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.

von Rudi (experte)


Lesenswert?

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.

von Martin M. (martin69)


Lesenswert?

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.

von Stefan F. (Gast)


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.

von Dennis S. (eltio)


Lesenswert?

Martin M. schrieb:
> Z.B. für Variablen mit Inhalt TRUE und FALSE (1 und 0)?
Warum in dem Fall kein boolean? 
https://en.cppreference.com/w/c/types/boolean

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ganz simpel, verwende
1
size_t

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


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.

von Stefan F. (Gast)


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.

von Falk B. (falk)


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.

von Falk B. (falk)


Lesenswert?

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!

von Veit D. (devil-elec)


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.

von Falk B. (falk)


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.

von Yalu X. (yalu) (Moderator)


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.

von Veit D. (devil-elec)


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

von Veit D. (devil-elec)


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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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?

von Rolf M. (rmagnus)


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.

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


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

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


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.

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


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


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.

von Walter K. (walter_k488)


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!

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Gedanklich passt das nicht zusammen. Und was schon gedanklich nicht
> zusammenpasst sollte man auch nicht Programmieren.  ;-)

S.a. mein Beitrag oben

von Veit D. (devil-elec)


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.

von Stefan F. (Gast)


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.

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


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.

von Veit D. (devil-elec)


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.

von Veit D. (devil-elec)


Lesenswert?

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.

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


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.

von Stefan F. (Gast)


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.

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


Lesenswert?

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

von Wilhelm M. (wimalopaan)


Lesenswert?

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

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Bis hierher okay. Nur das Ergebnis der Rechnung ist noch nicht der
> endgültige Wert für den Indexzugriff.

Wie meinst Du das?

von Veit D. (devil-elec)


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.

von Wilhelm M. (wimalopaan)


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

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


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

Sehe ich genauso.

von Veit D. (devil-elec)


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.

von Arduino F. (Firma: Gast) (arduinof)


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.

von Wilhelm M. (wimalopaan)


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

von Wilhelm M. (wimalopaan)


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!

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Wilhelm M. schrieb:
> Jungs: das ist doch Standard!
Ich danke dir dafür, dass du meine Aussage bestätigst.

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


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.

von Rolf M. (rmagnus)


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.

von Veit D. (devil-elec)


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.

von Udo K. (udok)


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

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


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

von Rainer W. (rawi)


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

: Bearbeitet durch User
von Stefan F. (Gast)


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.

von Michael D. (nospam2000)


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

: Bearbeitet durch User
von Rahul D. (rahul)


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.

von Zino (zinn)


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.

von Wilhelm M. (wimalopaan)


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

von Rolf M. (rmagnus)


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.

: Bearbeitet durch User
von Rolf (rolf22)


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

von Wilhelm M. (wimalopaan)


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.

von Rolf M. (rmagnus)


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.

: Bearbeitet durch User
von Yalu X. (yalu) (Moderator)


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.

von Wilhelm M. (wimalopaan)


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.

von Yalu X. (yalu) (Moderator)


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.

von Veit D. (devil-elec)


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.

von Wilhelm M. (wimalopaan)


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.

von Veit D. (devil-elec)


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.

von Harry L. (mysth)


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

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


Lesenswert?

Hallo Harry,

das könnte man als Schlusswort so stehen lassen.  :-)

von Yalu X. (yalu) (Moderator)


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.

von Yalu X. (yalu) (Moderator)


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.

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


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

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

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):
1
    printf("p[%d] = %2d\n", -1, (-1)[p]);

von Wilhelm M. (wimalopaan)


Lesenswert?

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!

von Frank M. (ukw) (Moderator) Benutzerseite


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.

: Bearbeitet durch Moderator
von Yalu X. (yalu) (Moderator)


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

von Wilhelm M. (wimalopaan)


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

von Stefan F. (Gast)


Lesenswert?

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.

von Udo K. (udok)


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

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.