?
Ich bin in Fällen wie diesem, in denen es leicht vermeidbar ist, immer dagegen, zwanzig Millionen Speicherblöcke einzeln anzufordern - wenn man tatsächlich mal in Probleme läuft, ist die Fehlerbehandlung auf diese Weise grauenvoll.
Ich habe hier vor längerer Zeit schon mal einen Ansatz vorgeschlagen, der dem Anfänger zunächst vielleicht etwas wild erscheinen mag, aber diese Problematik umschifft: Type-Punning. Ich habe eine einfache Beispielimplementation noch hier herumliegen:
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct string_array {
size_t size;
char **data;
};
struct string_array *get_file_contents(char const *fname) {
char buf[16 * 1024];
char *p;
FILE *fd;
size_t i;
size_t line_count = 1;
size_t file_size = 0;
size_t bytes_read;
struct string_array *result;
fd = fopen(fname, "r");
if(fd == NULL) return NULL;
/*
* Zwei-Pass-Verfahren: Erst die Zeilen zählen, um dann den großen Plan (s.u.)
* umsetzen zu können.
*/
do {
p = buf;
bytes_read = fread(buf, 1, sizeof(buf), fd);
file_size += bytes_read;
while((p = memchr(p, '\n', buf + bytes_read - p))) {
++p;
++line_count;
}
} while(bytes_read != 0);
rewind(fd);
/*
* Der Plan ist, den Array-Kopf, den Index und die Daten direkt hintereinander in
* einen großen Speicherblock zu legen, d.h.
*
* | size | data | i1 | i2 | i3 | ... | "foo" | "bar" | "baz" | ... |
* +------+------+----+----+----+-----+-------+-------+-------+-----+
* |___^ | | | ^ ^ ^
* |____|____|_________| | |
* |____|_________________| |
* |_________________________|
*/
result = malloc(sizeof(struct string_array) + /* Kopf */
sizeof(char*) * line_count + /* Index */
file_size + 1); /* Inhalt */
if(result == NULL) return NULL;
result->size = line_count;
result->data = (char**) (result + 1); /* Der Index liegt direkt hinter dem Struct */
result->data[0] = (char* ) (result->data + line_count); /* und die Daten direkt hinter dem Index */
p = result->data[0];
/* Gesamte Datei auf einmal einlesen */
bytes_read = fread(p, 1, file_size, fd);
assert(file_size == bytes_read);
fclose(fd);
p[file_size] = '\0';
/* Dann Zeilenumbrüche durch '\0' ersetzen (Also die Zeilen voneinander trennen)
* und den Index aufbauen: */
for(i = 1; i < line_count; ++i) {
p = memchr(p, '\n', result->data[0] + file_size - p); /* Wir wissen, dass das nie NULL wird */
*p++ = '\0';
result->data[i] = p;
}
/* Eine leere letzte Zeile wird per Konvention nicht als Zeile aufgefasst. */
if(*result->data[result->size - 1] == '\0') --result->size;
return result;
}
int main(int argc, char *argv[]) {
struct string_array *file_contents;
size_t i;
file_contents = get_file_contents(argc > 1 ? argv[1] : __FILE__);
if(file_contents == NULL) return -1;
for(i = 0; i < file_contents->size; ++i) {
puts(file_contents->data[i]);
}
/*
* Freigabe auf einen Schlag hier.
*/
free(file_contents);
return 0;
}
Wie man sieht, entfällt die malloc-bezogene Fehlerbehandlung nahezu vollständig, weil nur einmal Speicher angefordert wird. Der Nachteil hier ist, dass die Datei zweimal durchlaufen wird; in der Zeit der Kassettenlaufwerke hätte sich so etwas verboten, heute ist es nicht pauschal zu sagen, ob das ein Problem sein kann. Ggf. ist die Problematik nicht allzu schwer zu umgehen (fstat bzw. _fstat sind dein Freund), ich habe mich bislang nur nie hingesetzt und es gemacht.
Technisches P.S.: Probleme mit dem Alignment treten nicht auf, weil alle Zeiger die gleiche Größe und die gleiche Alignmentanforderung haben. data in struct string_array muss für einen Zeiger richtig ausgerichtet sein, also muss es auch der Speicher direkt dahinter sein. Darauf folgen nur chars, und die haben ein Alignment von 1 (Alignment kann nie größer als der Typ selbst sein, sonst wären Arrays unmöglich).