webentwicklung-frage-antwort-db.com.de

Kann calloc () insgesamt mehr als SIZE_MAX zuweisen?

In eine kürzlich durchgeführte Überprüfung des Codes wurde behauptet, dass

Auf ausgewählten Systemen kann calloc() mehr als SIZE_MAX Gesamtbytes zuordnen, während malloc() begrenzt ist.

Meine Behauptung ist, dass dies falsch ist, weil calloc() Platz für ein Array von Objekten schafft - was als Array selbst ein Objekt ist. Und kein Objekt kann größer als SIZE_MAX sein.

Wer von uns ist also richtig? Auf einem (möglicherweise hypothetischen) System mit einem Adressraum, der größer als der Bereich von size_t ist, darf calloc() erfolgreich sein, wenn mit Argumenten aufgerufen wird, deren Produkt größer ist als SIZE_MAX?

Um es konkreter zu machen: Wird das folgende Programm jemals mit einem Nicht-Null-Status beendet?

#include <stdint.h>
#include <stdlib.h>

int main()
{
     return calloc(SIZE_MAX, 2) != NULL;
}
29
Toby Speight

SIZE_MAX gibt nicht notwendigerweise die maximale Größe eines Objekts an, sondern den Maximalwert von size_t, der nicht unbedingt derselbe ist. Siehe Warum ist die maximale Größe eines Arrays "zu groß"?

Natürlich ist es nicht klar definiert, einen größeren Wert als SIZE_MAX an eine Funktion zu übergeben, die einen size_t-Parameter erwartet. Theoretisch ist SIZE_MAX die Grenze, und theoretisch würde calloc die Zuordnung von SIZE_MAX * SIZE_MAX-Bytes erlauben.

Die Sache mit malloccalloc besteht darin, dass sie Objekte ohne Typ zuordnen. Objekte mit einem Typ unterliegen Einschränkungen, z. B. niemals größer als ein bestimmter Grenzwert wie SIZE_MAX. Die Daten, auf die das Ergebnis dieser Funktionen zeigt, haben jedoch keinen Typ. Es ist (noch) kein Array.

Formal haben die Daten keinen _/deklarierten Typ, aber wenn Sie etwas in den zugewiesenen Daten speichern, erhalten sie den effektiven Typ des Datenspeichers, der zum Speichern verwendet wird (C17 6.5 §6).

Dies bedeutet wiederum, dass es calloc möglich wäre, mehr Speicher zuzuordnen, als jeder Typ in C enthalten kann, da das, was zugewiesen wird, (noch) keinen Typ hat.

Daher ist es für den C-Standard völlig in Ordnung, dass calloc(SIZE_MAX, 2) einen von NULL verschiedenen Wert zurückgibt. Wie man diesen zugewiesenen Speicher sinnvoll nutzt oder welche Systeme sogar so große Speicherbrocken auf dem Heap unterstützen, ist eine andere Geschichte.

15
Lundin

Kann calloc () insgesamt mehr als SIZE_MAX zuweisen?

Als die Behauptung "Auf ausgewählten Systemen kann calloc() mehr als SIZE_MAX Gesamtbytes zuweisen, wohingegen malloc() begrenzt ist." kam aus einem Kommentar Ich habe gepostet, ich werde meine Gründe erläutern.


size_t

size_t ist irgendein unsigned-Typ mit mindestens 16 Bits.

size_t ist der vorzeichenlose Integer-Typ des Ergebnisses des sizeof-Operators. C11dr §7.19 2

"Der implementierungsdefinierte Wert muss größer oder gleich dem entsprechenden Wert sein."........ Begrenzung von size_tSIZE_MAX ... 65535 §7.20.3 2

Größe von

Der Operator sizeof gibt die Größe (in Bytes) seines Operanden an, der ein .__ sein kann. Ausdruck oder der Name in Klammern eines Typs. §6.5.3.4 2

calloc

void *calloc(size_t nmemb, size_t size);

Die calloc-Funktion reserviert Platz für ein Array von nmemb-Objekten, von denen jedes size die Größe hat. §7.22.3.2 2


Stellen Sie sich eine Situation vor, in der nmemb * sizeSIZE_MAX deutlich überschreitet.

size_t alot = SIZE_MAX/2;
double *p = calloc(alot, sizeof *p); // assume `double` is 8 bytes.

Wenn calloc() wirklich nmemb * size Bytes zugewiesen wurde und wenn p != NULL wahr ist, gegen welche Spezifikation hat dies verstoßen?

Die Größe jedes Elements (jedes Objekts) ist darstellbar.

// Nicely reports the size of a pointer and an element.
printf("sizeof p:%zu, sizeof *p:%zu\n", sizeof p, sizeof *p); 

Auf jedes Element kann zugegriffen werden.

// Nicely reports the value of an `element` and the address of the element
for (size_t i = 0; i<alot; i++) {
  printf("value a[%zu]:%g, address:%p\n", i, p[i], (void*) &p[i]); 
}

calloc() Details

"Platz für ein Array von nmemb-Objekten": Dies ist sicherlich ein zentraler Konfliktpunkt. Benötigt der "Speicherplatz für das Array" <= SIZE_MAX? Ich habe nichts in der C-Spezifikation gefunden, um dieses Limit zu fordern, und folgere daraus:

calloc() kann insgesamt mehr als SIZE_MAX zuweisen.


Es ist sicherlich ungewöhnlich für calloc() mit umfangreichen Argumenten, dass sie nicht -NULL-konform zurückgeben oder nicht. Normalerweise übersteigen solche Zuweisungen den verfügbaren Speicherplatz, so dass das Problem irrelevant ist. Der einzige Fall, dem ich begegnet bin, war das Huge-Memory-Modell , bei dem size_t 16 Bit und der Objektzeiger 32 Bit war.

20
chux

Nur eine Ergänzung: Mit ein bisschen Mathematik können Sie zeigen, dass SIZE_MAX * SIZE_MAX = 1 ist (bei Auswertung nach C-Regeln). 

Calloc (SIZE_MAX, SIZE_MAX) darf jedoch nur eine der beiden folgenden Aktionen ausführen: Einen Zeiger auf ein Array von SIZE_MAX-Elementen von SIZE_MAX-Bytes zurückgeben, OR gibt NULL zurück. Es ist NICHT erlaubt, die Gesamtgröße zu berechnen, indem Sie einfach die Argumente multiplizieren, ein Ergebnis von 1 erhalten und ein Byte zuweisen, das auf 0 gelöscht wird.

2
gnasher729

Wenn ein Programm die Implementierungsgrenzen überschreitet, ist das Verhalten undefiniert. Dies ergibt sich aus der Definition einer Implementierungsgrenze als einer durch die Implementierung für Programme auferlegten Einschränkung (3.13 in C11). Der Standard besagt auch, dass streng konforme Programme die Umsetzungsgrenzen einhalten müssen (4p5 in C11). Dies gilt jedoch auch für Programme im Allgemeinen, da der Standard nicht sagt, was passiert, wenn die meisten Implementierungsgrenzen überschritten werden (es ist also die andere Art undefiniertes Verhalten, bei der der Standard nicht angibt, was passiert).

Der Standard definiert auch nicht, welche Implementierungsgrenzen möglicherweise existieren, daher ist dies ein bisschen von carte blanche , aber ich denke, es ist vernünftig, dass die maximale Objektgröße tatsächlich für Objektzuordnungen relevant ist. (Die maximale Objektgröße ist übrigens normalerweise kleiner als SIZE_MAX, da die Differenz von Zeigern auf -char innerhalb des Objekts in ptrdiff_t darstellbar sein muss.)

Dies führt zu der folgenden Beobachtung: Ein Aufruf von calloc (SIZE_MAX, 2) überschreitet die maximale Objektgrößengrenze, sodass eine Implementierung einen willkürlichen Wert zurückgeben kann, während sie dennoch dem Standard entspricht.

Einige Implementierungen geben tatsächlich einen Zeiger zurück, der für einen Aufruf wie calloc (SIZE_MAX / 2 + 2, 2) nicht null ist, da die Implementierung nicht prüft, ob das Multiplikationsergebnis nicht in einen size_t-Wert passt. Ob dies eine gute Idee ist, ist eine andere Sache, da in diesem Fall die Implementierungsgrenze so einfach überprüft werden kann und es eine sehr gute Möglichkeit gibt, Fehler zu melden. Ich persönlich halte das Fehlen einer Überlaufprüfung in calloc für einen Implementierungsfehler und habe Fehler an Implementierer gemeldet, als ich sie gesehen habe, aber technisch gesehen handelt es sich lediglich um ein Qualitätsproblem.

Für Arrays mit variabler Länge auf dem Stack ist die Regel, dass die Implementierungsgrenzwerte überschritten werden, die zu undefiniertem Verhalten führen, offensichtlicher:

size_t length = SIZE_MAX / 2 + 2;
short object[length];

Eine Implementierung kann hier wirklich nichts tun, daher muss sie undefiniert sein.

2
Florian Weimer

Je nach Text des Standards vielleicht, weil der Standard (manche würden absichtlich sagen) vage zu dieser Art von Dingen.

Nach 6.5.3.4 ¶2:

Der Operator sizeof gibt die Größe (in Bytes) seines Operanden an

und gemäß 7.19 ¶2:

size_t

dies ist der vorzeichenlose Integer-Typ des Ergebnisses des sizeof-Operators.

Ersteres kann im Allgemeinen nicht erfüllt werden, wenn die Implementierung einen beliebigen Typ (einschließlich Array-Typen) zulässt, dessen Größe in size_t nicht darstellbar ist. Unabhängig davon, ob Sie den Text über den Zeiger interpretieren, der von calloc zurückgegeben wird und auf "ein Array" zeigt, ist für jedes Objekt immer ein Array vorhanden: das überlagerte Array vom Typ unsigned char[sizeof object], dessen Darstellung ist.

Im besten Fall hat eine Implementierung, die die Erstellung von Objekten ermöglicht, die größer als SIZE_MAX (oder PTRDIFF_MAX aus anderen Gründen) sind, schwerwiegende QoI-Probleme (Implementierungsqualität). Die Behauptung auf Codeüberprüfung, dass Sie solche schlechten Implementierungen berücksichtigen sollten, ist falsch, es sei denn, Sie versuchen ausdrücklich, die Kompatibilität mit einer bestimmten defekten C-Implementierung sicherzustellen (die manchmal für Embedded-Anwendungen relevant ist).

2
R..

Von

7.22.3.2 Die Calloc-Funktion

Zusammenfassung

 #include <stdlib.h>
 void *calloc(size_t nmemb, size_t size);`

Beschreibung
2 Die Calloc-Funktion reserviert Platz für ein Array von nmemb-Objekten, von denen jedes size die Größe hat. Der Speicherplatz wird auf alle Bits Null initialisiert.

Kehrt zurück
3 Die Calloc-Funktion gibt entweder einen Nullzeiger oder einen Zeiger auf den zugewiesenen Speicherplatz zurück.

Ich kann nicht erkennen, warum der zugewiesene Speicherplatz auf SIZE_MAX Bytes beschränkt sein sollte.

2
Swordfish

Der Standard sagt nichts darüber aus, ob es möglich sein könnte, dass ein Zeiger irgendwie erstellt wird, sodass ptr+number1+number2 ein gültiger Zeiger sein könnte, number1+number2 jedoch SIZE_MAX überschreiten würde. Es erlaubt sicherlich die Möglichkeit, dass number1+number2PTRDIFF_MAX überschreitet (obwohl C11 aus irgendeinem Grund beschlossen hat, dass selbst Implementierungen mit einem 16-Bit-Adressraum einen 32-Bit-_ptrdiff_t verwenden müssen).

Der Standard schreibt nicht vor, dass Implementierungen alle Möglichkeiten bieten, Zeiger auf solche großen Objekte zu erstellen. Sie definiert jedoch eine Funktion, calloc(), deren Beschreibung darauf hindeutet, dass sie aufgefordert werden könnte, ein solches Objekt zu erstellen, und würde vorschlagen, dass calloc() einen Nullzeiger zurückgibt, wenn das Objekt nicht erstellt werden kann.

Die Möglichkeit, jede Art von Objekten sinnvoll zuzuordnen, ist jedoch ein Problem der Qualität der Implementierung. Der Standard würde niemals verlangen, dass eine bestimmte Zuordnungsanforderung erfolgreich ist, und er würde auch nicht zulassen, dass eine Implementierung einen Zeiger zurückgibt, der sich als unbrauchbar erweisen könnte (in einigen Linux-Umgebungen kann ein malloc () einen Zeiger auf einen übermäßig festgeschriebenen Bereich von geben Adressraum; ein Versuch, den Zeiger zu verwenden, wenn nicht genügend physischer Speicher verfügbar ist, kann zu einem schwerwiegenden Trap führen. Es wäre sicherlich besser für eine nicht kapriziöse Implementierung von calloc(x,y) den Wert null zurückzugeben, wenn das numerische Produkt von x und y SIZE_MAX überschreitet, als wenn es einen Zeiger gibt, mit dem nicht auf diese Anzahl von Bytes zugegriffen werden kann. Der Standard sagt jedoch nichts darüber aus, ob die Rückgabe eines Zeigers, der für den Zugriff auf y-Objekte von x-Bytes verwendet werden kann, besser oder schlechter ist als die Rückgabe von null. Jedes Verhalten wäre in einigen Situationen vorteilhaft und in anderen nachteilig.

0
supercat