C sizeof()-Operator spinnt?

@ antred: trotzdem haben arrays und pointer einiges gemeinsam. kern dieser unterhaltung war es die unterschiede hervorzuheben.

@joe: beim ersten print kommt 3 raus und beim zweiten immer etwas anderes, oder?
 
joe67 schrieb:
Damit die Verwirrung perfekt ist, hat das mal jemand von euch compiliert?

Code:
int a[5] = { 1, 2, 3, 4, 5};
int b = 2;

printf( "%d\n", a[b] );
printf( "%d\n", b[a] );

Funktioniert hier (VC++) einwandfrei und zeigt auf, dass Compiler Arrays im Endeffekt wieder wie Pointer behandeln. :)

SCNR.

j o e

Also, daß der 2. Teil funktioniert zeigt meiner Meinung nach nur eine schlampige Umsetzung des C++-Standards auf. Eigentlich sollte dir der Compiler da mehrere Morddrohungen entgegenschleudern.


EDIT: Nehme alles zurück. Mir ist gerade wieder eingefallen, daß arrayName[Index] das gleiche ist wie Index[ArrayName]. :o
 
Zuletzt bearbeitet:
foggy80 schrieb:
@joe: beim ersten print kommt 3 raus und beim zweiten immer etwas anderes, oder?

Einfach mal eintippen wenn es die Zeit erlaubt. Aber es kommt bei beiden das gleiche raus.

In beiden Fällen erkennt der Compiler, dass es sich um einen indizierten Zugriff handelt. Er addiert einfach a und b und interpretiert das Ergebnis als die Adresse an der er lesen soll.

Ich wollte aber hier nicht vom eigentlichen Thema ablenken, sondern lediglich aufzeigen, dass der Umgang mit Arrays und Pointern in C generell nicht unbedingt glücklich gelöst ist.

j o e
 
Um nochmal meinen Senf dazuzugeben:
e1[e2] ist äquivalent mit *(e1+e2). Die zweite Form erklärt auch absolut nachvollziehbar die Vertauschbarkeit (das Kommutativgesetz gilt).
 
die zuletzt von 7H3 N4C3R beschriebene äuqivalenz ist durchaus interessant. wenn ich also per malloc einen n-byte-großen speicher reserviere und z.B *speicher darauf zeigen lasse, kann ich mit *(speicher+i) für i von 0 bis n auf jeden byte des speichers zugreifen? und *(speicher+i) ist dann äuquivalent zu zeiger[n]?
 
von 0 bis n-1.. dass es äquivalent ist stimmt. array + index ergibt zeiger auf zelle index.
ACHTUNG: es wird häufiger der fehler gemacht dass array + index das gleiche ist wie basisadresse array + index (bytes). das ist aber falsch. hier ist die sizeof(array[0]) zu beachten! aus diesem grund funktioniert das auch nicht wenn array den typ void * hat, da void keine festgelegte größe bestitzt.
 
Genau, IceMatrix hat's schon gesagt. Einen typisierten Pointer zu inkrementieren, bedeutet ihn im Speicher um die Größe des Types in Bytes auf den der Pointer zeigt zu inkrementieren.

Unter der Annahme, dass man einen Pointer p auf int hat und int 4 Byte groß ist (letzteres muss nicht zwingend so sein), bedeutet p++ den Zeiger um 4 Byte im Speicher weiter zu schieben. Ist auch absolut logisch, weil man sonst nicht sinnvoll auf Arrays etc zugreifen könnte.

@IceMatrix: Das mit void kann u.U. verwirrend sein. :)
void hat insofern keine festgelegte Größe, als dass void ein unvollständiger Typ ist (und niemals vollständig werden kann). Die Größe von unvollständigen Typen kann nicht ermittelt werden und ein Inkrement auf so einen Pointer muss der Compiler mit einer Fehlermeldung quittieren.
(Die andere Interpretation wäre, dass die Größe nicht festgelegt ist, aber trotzdem irgendeinen Wert hat. Das ist nicht so. :))

@foggy80:
Falls du wirklich mal den Speicher Byte for Byte abgrasen willst, musst du vorher in einen signed char*/unsigned char*/char* casten, dieser hat die garantierte Größe von 1 Byte. Die so ermittelten Ergebnisse sind absolut unportabel und nur für Forschungszwecke, wie die Maschine auf der du bist funktioniert, interessant.
Edit:
Um Dir noch eine Warnung an die Hand zu geben: Du kannst zwar jeden Typ mal zum Ausprobieren mit einem char* untersuchen, indem du den Pointer bzw. die Adresse in char* umwandelst. Die andere Richtung ist jedoch problembehaftet. Typen haben nämlich ein Alignment. Als Beispiel, 4 Byte große int's leben i.a.R. an Speicheradressen, die glatt durch 4 teilbar sind. Castest du nun (um das Beispiel weiter zu spinnen) also einen Pointer, der nicht auf so eine 4-Byte-Grenze zeigt, in einen int* um und dereferenzierst ihn, ist das undefiniertes Verhalten. Im besten Falle merkt dein Betriebssystem das und emuliert diesen Zugriff in Software. Überlicherweise generiert der Prozessor bei so einem Zugriff aber eine Trap und das Programm stürzt ab. In beiden Fällen ist es aber ein harter Programmierfehler. Also Vorsicht hier! :)
U.a. aus diesem Grund gibt dir übrigens malloc Speicheradressen zurück, die an 16, 32, 64 (oder sogar noch mehr) -Byte-Grenzen ausgerichtet sind, selbst wenn du nur 1 Byte anforderst.
 
Zuletzt bearbeitet:
7H3 N4C3R schrieb:
U.a. aus diesem Grund gibt dir übrigens malloc Speicheradressen zurück, die an 16, 32, 64 (oder sogar noch mehr) -Byte-Grenzen ausgerichtet sind, selbst wenn du nur 1 Byte anforderst.

Ist das immer so, oder würde in embedded Systemen ein Compiler aufgrund von Speicherknappheit ein typabhängigen Alignment vorziehen?

j o e
 
@joe Steht so im Standard. malloc/realloc/calloc müssen eine Speicherbereich liefern, der so aligned ist, dass er für jeden Datentyp geeignet ist und dass man den Speicherbereich wie ein array von jedem Datentyp nutzen kann. Also muss grundsätzlich auf das größtmögliche Alignment angepasst werden. Daher diese unsäglich großen Blöcke für kleine Objekte.

Wobei es durchaus malloc-Implementierung gibt, die diesen Punkt ignorieren und wie bspw tcmalloc einen internen small-object-allocator nutzen, der bei kleinen Anfragen, dann nicht standardkonforme Antworten gibt.

Für den uC-Bereich kannst du daher auf nicht standardkonforme malloc-Implementierungen ausweichen oder eine eigene Speicherverwaltung betreiben. Letzteres kann sehr nützlich sein.
 
Zuletzt bearbeitet:
Je nach Embedded System ist ein malloc sogar verboten.
Im Automobilbereich bei den tief eingebetteten Systemen (Steuergeräte für Motor, Lenkung, Getriebe usw.) ist eine dynamische Anforderung von Speicher absolut tabu. Software, die dynamisch Speicher anfordert ist nicht mehr deterministisch, was bei den Winzlingen von Controllern (oft < 40 KiB RAM) sehr schnell zu Problemen führen kann. So muss bereits zur Compilezeit bekannt sein, wieviel Speicher die Anwendung benötigt.
 
Da malloc nur eine Größe kennt die es reservieren soll, und nicht etwa den Typ, kann malloc natürlich nicht wirklich optimieren. C++ hat hier durch das Typ-behaftete new definitiv die Nase vorn.
Wobei ein SmallObject-Allocator auch haarig sein kann, wenn man multithreaded Performance rausholen will und Objekte für verschiedene Threads auf einmal in der selben Cacheline liegen. Das ist dann doch ziemlich... kontraproduktiv. :)

@Boron: Das klingt mal richtig interessant! Naja, wäre auch blöd, wenn in einem Auto der OOM-Killer zuschlägt... :evillol:
 
char tabelle[10]; <-- ist kein zeiger, somit frag ich mich wie du auf die idee kommst dass bei ein sizeof() 4 herauskommen sollte.

du müsstest einen zeiger auf das array setzen, und dann ein sizeof zb. nen integer pointer machen dann haste 4 bytes, aber wenn du ohne zeiger ein sizeof machst, dann haste natürlich 10*1 bytes. (auch wenn die elemente null sind).
pufferd issn pointer wie man unschwer an * erkennt. somit 4 bytes
außerdem isses aber unsafe code was ungut is
 
Zuletzt bearbeitet:
@Boron Das mit dem Determinismus ist nur bedingt richtig. Man kann auch dynamische Speicherallokation deterministisch gestalten. Nur ist der Nachweis des Determinismuses meist wesentlich aufwendiger, als wenn man gleich ganz darauf verzichtet. Gleiches Spiel mit den Rekursionen.

Mal ganz abgesehen davon, dass ich bei unter einem 1k Speicher oder die ganz niedliche frei RAM eh nicht weiß, wozu man malloc brauchen soll. Bei 40k oder 50k kann man das aber schon mal machen, solange die Spezifikationen es zu lassen.
 
Boron schrieb:
Je nach Embedded System ist ein malloc sogar verboten.

Kenn ich. Ich bin im Bereich Sicherheitstechnik beschäftigt und da ist dynamische Speicherverwaltung ebenfalls ein absolutes Tabu.

@7H3 N4C3R: Richtig, malloc kennt den Typ ja gar nicht, daran hatte ich nicht gedacht. Kommt davon wenn man es nie benutzt. Das Problem mit Alignment ist mir jedoch bei der Verwendung von struct bekannt und kann abhängig vom Compiler zu unterschiedlichen Ergebnissen führen.

j o e
 
@flopalko:
Nein, ist er nicht.
Es fühlt sich durch die fast allgegenwärtige, implizite Array-To-Pointer-Conversion so an, aber es ist nicht das gleiche. (außerdem lohnt es sich glaube ich nicht auf Beiträge einzugehen, wenn der Ersteller offensichtlich den Thread nicht gelesen hat...)

Hier nochmal der C-Standard, um das eh schon viel gepredigte "Arrays sind keine Pointer" zu unterstreichen:
ISO 9899 - 6.3.2.1 (3) 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 with type ‘‘pointer to type’’ that points to the initial element of the array object and is not an lvalue. If the array object has register storage class, the behavior is undefined.
 
Zuletzt bearbeitet:
Ja Arrays sind keine Pointer, das ist richtig, trotzdem gehört zu einem Array ein Pointer!
Die Daten werden im Speicher auch so abgelegt

[POINTER]
[ELEMENT 1]
[ELEMENT 2]
[...]
[ELEMENT n]

Der Pointer zeigt hier normal auf die Adresse des ersten Elements. Wenn man nun auf ein bestimmtes Element zugreifen möchte wird auf die Adresse auf die der Pointer zeigt noch die Größe des Datentyps der Elemente addiert und somit der Zugriff auf ein spezifisches Element ermöglicht. Für die Ermittlung des Typs werden wenn ich mich recht erinnere auch die sizeof Operationen verwendet(Also intern ohne das man da selber Hand anlegen muss natürlich)!?!

Bei der Verwendung von sizeof wird allerdings die Größe des Typs ermittelt wo es keine Rolle Spiel das ein Array auch einen Pointer enthält(Das was man halt und ist auch uninteressant...)
 
@Fonce Richtig ist: Ein Array sieht im Speicher so aus:
[E1]
[E2]
[E3]
....

In der array-Variable ist analog zu einer normalen Variable hinterlegt, wo der Beginn ist. Das zusammen mit der von 7H3 N4C3R erwähnten impliziten Umwandlung, führt gerne die Verwirrung, das es ein pointer ist.
Bei einem pointer auf einem Speicherbereich, sieht es aber anders aus. Da steht an der Stelle, auf die die Variable verweist, ein Adresse, die auf einen anderen Bereich verweist.

D.h.
Bei einem int:
Variable -> int-wert
Beim einem echten Array:
Variable -> array
Bei einem Pointer auf einen Speicherbereich, der als Array genutzt wird bzw. bei einem variablen Array:
Variable -> Pointer -> Array

Die Zuordnung Variable auf Speicherstelle nimmt dein Compiler und nur der vor. Er legt diese Adresse direkt oder aber auf eine Basis-Adresse bezogen im Code ab und ruft sie dann bei Bedarf ab.
 
Zurück
Oben