Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Stackproblem: main vs. Unter-Funktion

4 views
Skip to first unread message

wolfgang bauer (D)

unread,
Aug 2, 2022, 5:04:30 AM8/2/22
to

Hallo

Ich programmiere unter Linux-Mint 19.3 mit dem gcc.

Dabei habe ich folgendes Problem mit einem Konsolenprogramm, an dem ich arbeite.
Lade ich eine mehrere MByte große Datei in den Speicher, gibt es bei schreibendem Zugriff darauf diesen
Fehler: "*** stack smashing detected ***: <unknown> terminated"

Dies geschieht aber nur, wenn ich den Speicher in einer Funktion verarbeite.

Realisiere ich alles in main, gibt es keine Probleme.

Hat jemand einen Tip, was ich tun könnte ?

Mit einer wesentlich kleineren Datei funktioniert beides: Sowohl in main als auch in einer Funktion.

Nur zur Sicherheit: Mein RAM beträgt 16 GB. Daran kanns nicht liegen.



--
Gruß, Greetings

Thomas Koenig

unread,
Aug 2, 2022, 6:40:33 AM8/2/22
to
wolfgang bauer (D) <sch...@gmx.de> schrieb:
>
> Hallo
>
> Ich programmiere unter Linux-Mint 19.3 mit dem gcc.
>
> Dabei habe ich folgendes Problem mit einem Konsolenprogramm, an dem ich arbeite.
> Lade ich eine mehrere MByte große Datei in den Speicher, gibt es bei schreibendem Zugriff darauf diesen
> Fehler: "*** stack smashing detected ***: <unknown> terminated"

Ohne Source kann man da sehr wenig sagen. Nur ein paar Fragen...

Wo liegt der Array, in dem du die Datei zwischenspeicherst? In einem
lokalen Array oder in einem Speicherbereich, der dynamisch (mit malloc
oder calloc) allokiert wurde?

Oder verwendest du alloca? Da kann der Stack schnell überlaufen.

Der Stack ist bei modernen Systemen typischerweise begrenzt (meiner
Meinung nach viel zu heftig), was sagt "ulimit -s" ?

> Dies geschieht aber nur, wenn ich den Speicher in einer Funktion verarbeite.
>
> Realisiere ich alles in main, gibt es keine Probleme.
>
> Hat jemand einen Tip, was ich tun könnte ?

Den Fehler beheben :-)

Für spezifischere Tips musst du mehr von deinem Source zeigen.

> Mit einer wesentlich kleineren Datei funktioniert beides: Sowohl in main als auch in einer Funktion.

Könnte tatsächlich ein Stacküberlauf sein.

> Nur zur Sicherheit: Mein RAM beträgt 16 GB. Daran kanns nicht liegen.

Da hast du recht.

wolfgang bauer (D)

unread,
Aug 2, 2022, 7:47:39 AM8/2/22
to
02.08.22 , 12:40 , Thomas Koenig:

>> Dabei habe ich folgendes Problem mit einem Konsolenprogramm, an dem ich arbeite.
>> Lade ich eine mehrere MByte große Datei in den Speicher, gibt es bei schreibendem Zugriff darauf diesen
>> Fehler: "*** stack smashing detected ***: <unknown> terminated"
>
> Ohne Source kann man da sehr wenig sagen. Nur ein paar Fragen...

Das Programm ist etwas größer. Wenn erforderlich dampfe ich es auf das Nötigste ein um es hier zu posten. Danke auf jeden
Fall für deine Hilfsbereitschaft !



> Wo liegt der Array, in dem du die Datei zwischenspeicherst? In einem
> lokalen Array oder in einem Speicherbereich, der dynamisch (mit malloc
> oder calloc) allokiert wurde?

Ich reserviere den Speicher mit malloc.



> Der Stack ist bei modernen Systemen typischerweise begrenzt (meiner
> Meinung nach viel zu heftig), was sagt "ulimit -s" ?

8192


--
Gruß, Greetings

Bonita Montero

unread,
Aug 2, 2022, 8:17:42 AM8/2/22
to
Stack Smashing bedeutet einfach, dass Du irgendwo über die
Stack Bounds eines Stack Frames hinweggeschrieben hast.
Schalt das einfach aus und gut is. Hrhr.

wolfgang bauer (D)

unread,
Aug 2, 2022, 9:18:47 AM8/2/22
to
02.08.22 , 14:18 , Bonita Montero:

> Stack Smashing bedeutet einfach, dass Du irgendwo über die
> Stack Bounds eines Stack Frames hinweggeschrieben hast.

Mir wäre neu, das man mit per malloc beschafftem Speicher ein Stackproblem bekommen kann.
Die Testdatei umfasst gerade mal 4.3 MB.

> Schalt das einfach aus und gut is. Hrhr.

Ja, hrhr. Wärs so einfach, täte ich hier nicht fragen tun.

Oder ich sehe den Wald vor Bäumen nicht, was ja manchmal vorkommen kann.

Wieviel Zeilen Beispielcode sind hier in der Gruppe erlaubt ?



--
Gruß, Greetings

Bonita Montero

unread,
Aug 2, 2022, 9:54:43 AM8/2/22
to
Am 02.08.2022 um 15:18 schrieb wolfgang bauer (D):

> Mir wäre neu, das man mit per malloc beschafftem Speicher ein Stackproblem bekommen kann.
> Die Testdatei umfasst gerade mal 4.3 MB.

Naja, sonst würd der Fehler sicher nicht als Stack Smashing Fehler
ausgelegt. D.h. Du untersuchst das Problem ggf. an der verkehrten
Seite.

wolfgang bauer (D)

unread,
Aug 2, 2022, 11:30:29 AM8/2/22
to


Das war ein Fehlalarm. Ich habe den Fehler gefunden. In einer Funktion hatte ich einen Temp.Buffer zu klein
angelegt. In diesen lese ich Text-Zeilen ein. Ist eine Zeile zu lang, dann knallt es eben.

Das ist einer dieser Fehler, wie man sie hassen muss, weil sie auf die falsche Spur führen. Nach
dem Gesetz von Murphy enthielt die größere Testdatei "zufällig" zu lange Zeilen. Die kleinere Datei
nicht.

Mit der Dateigröße hatte das also nichts zu tun. Bleibt nur der interessante Punkt, das solche Fehler
in main keine Wirkung zeigen (wohl nur solange ich nicht zu weit in verbotenen Speicher schreibe), wohl
aber in einer Unterfunktion..





--
Gruß, Greetings

Thomas Koenig

unread,
Aug 2, 2022, 11:31:05 AM8/2/22
to
wolfgang bauer (D) <sch...@gmx.de> schrieb:
> 02.08.22 , 12:40 , Thomas Koenig:
>
>>> Dabei habe ich folgendes Problem mit einem Konsolenprogramm, an dem ich arbeite.
>>> Lade ich eine mehrere MByte große Datei in den Speicher, gibt es bei schreibendem Zugriff darauf diesen
>>> Fehler: "*** stack smashing detected ***: <unknown> terminated"
>>
>> Ohne Source kann man da sehr wenig sagen. Nur ein paar Fragen...
>
> Das Programm ist etwas größer. Wenn erforderlich dampfe ich es auf das Nötigste ein um es hier zu posten. Danke auf jeden
> Fall für deine Hilfsbereitschaft !
>
>
>
>> Wo liegt der Array, in dem du die Datei zwischenspeicherst? In einem
>> lokalen Array oder in einem Speicherbereich, der dynamisch (mit malloc
>> oder calloc) allokiert wurde?
>
> Ich reserviere den Speicher mit malloc.

Hmm...

dann würde ich vorschlagen, dass du mal an Tools draufwirfst, was
du hast.

Installiere mal valgrind, kompiliere dein Programm mit "-g"
und lasse valgrind drüberlaufen. Da kommen dann z.B. solche
Meldungen für ein fehlerhaftes Programm:

$ cat main.c
#include <stdio.h>
#include <stdlib.h>

#define N 10

int main()
{
int *a, s;
a = malloc(N);
for (int i=0; i <=N; i++)
{
a[i] = i;
}
s = 0;
for (int i=0; i<= N; i++)
{
s = s + a[i];
}
printf ("%d\n", s);
return 0;
}
$ gcc -g main.c
$ valgrind ./a.out
==2962== Memcheck, a memory error detector
==2962== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==2962== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==2962== Command: ./a.out
==2962==
==2962== Invalid write of size 4
==2962== at 0x1091A3: main (main.c:12)
==2962== Address 0x4a63048 is 8 bytes inside a block of size 10 alloc'd
==2962== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2962== by 0x10917E: main (main.c:9)
==2962==
==2962== Invalid read of size 4
==2962== at 0x1091D3: main (main.c:17)
==2962== Address 0x4a6304c is 2 bytes after a block of size 10 alloc'd
==2962== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2962== by 0x10917E: main (main.c:9)

[...]

Helmut Schellong

unread,
Aug 2, 2022, 11:39:10 AM8/2/22
to
On 08/02/2022 17:30, wolfgang bauer (D) wrote:
>
>
> Das war ein Fehlalarm. Ich habe den Fehler gefunden. In einer Funktion hatte ich einen Temp.Buffer zu klein
> angelegt. In diesen lese ich Text-Zeilen ein. Ist eine Zeile zu lang, dann knallt es eben.
>
> Das ist einer dieser Fehler, wie man sie hassen muss, weil sie auf die falsche Spur führen. Nach
> dem Gesetz von Murphy enthielt die größere Testdatei "zufällig" zu lange Zeilen. Die kleinere Datei
> nicht.
>
>

Ein Puffer für unbekannte Zeilen sollte heutzutage 1 MB fassen können.
Einfach im Stack anlegen; ist schnell und sicher.


--
Mit freundlichen Grüßen
Helmut Schellong v...@schellong.biz
http://www.schellong.de/c.htm http://www.schellong.de/c2x.htm http://www.schellong.de/c_padding_bits.htm
http://www.schellong.de/htm/bishmnk.htm http://www.schellong.de/htm/rpar.bish.html http://www.schellong.de/htm/sieger.bish.html
http://www.schellong.de/htm/audio_proj.htm http://www.schellong.de/htm/audio_unsinn.htm http://www.schellong.de/htm/tuner.htm
http://www.schellong.de/htm/string.htm http://www.schellong.de/htm/string.c.html http://www.schellong.de/htm/deutsche_bahn.htm
http://www.schellong.de/htm/schaltungen.htm http://www.schellong.de/htm/rand.htm http://www.schellong.de/htm/dragon.c.html

wolfgang bauer (D)

unread,
Aug 2, 2022, 11:41:47 AM8/2/22
to
02.08.22 , 17:31 , Thomas Koenig:

> Installiere mal valgrind, kompiliere dein Programm mit "-g"

Danke für den Tip. Mit valgrind habe ich noch nicht gearbeitet. Ich werde es mir ansehen.

Aber wie weiter unten schon gesagt, ist das Problem gelöst.


--
Gruß, Greetings

Rainer Weikusat

unread,
Aug 2, 2022, 1:23:02 PM8/2/22
to
Helmut Schellong <r...@schellong.biz> writes:
> On 08/02/2022 17:30, wolfgang bauer (D) wrote:
>>
>>
>> Das war ein Fehlalarm. Ich habe den Fehler gefunden. In einer Funktion hatte ich einen Temp.Buffer zu klein
>> angelegt. In diesen lese ich Text-Zeilen ein. Ist eine Zeile zu lang, dann knallt es eben.
>>
>> Das ist einer dieser Fehler, wie man sie hassen muss, weil sie auf die falsche Spur führen. Nach
>> dem Gesetz von Murphy enthielt die größere Testdatei "zufällig" zu lange Zeilen. Die kleinere Datei
>> nicht.
>>
>>
>
> Ein Puffer für unbekannte Zeilen sollte heutzutage 1 MB fassen können.
> Einfach im Stack anlegen; ist schnell und sicher.

Einen Puffer für unbekannte Zeilen sollte man gar nicht benutzen, weder
1M noch in sonst einer Größe. Textdateien muß man parsen. Sogar, wenn
man nur Zeilen heraushaben möchte. Wenn man es sich leicht machen will,
kann man die kompletten Eingaben in den Speicher lesen und dann in place
nach \n durchsuchen.

Andernfalls kann man den nächsten Eingabeblock in einen Puffer einer
festen Größe lesen und sich daraus Stück für Stück Zeilen variabler
Größe in einem geeignet wachsenden Puffer zusammensetzen. ZB so:

--------
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static char *read_line(FILE *f)
{
static char buf[16], *p, *e;
size_t nr, need, ofs, total;

struct {
char *s, *p, *e;
} l;

l.s = l.p = malloc(33);
l.e = l.s + 32;

nr = e - p;
if (nr) {
memcpy(l.s, p, nr);
l.p += nr;
}

p = e = buf;

while (nr = fread(buf, 1, sizeof(buf), f)) {
e = buf + nr;
while (*p != '\n' && p < e) ++p;

need = p - buf;
if (need > l.e - l.p) {
ofs = l.p - l.s;

total = l.e - l.s;
do total *= 2; while (total < ofs + need);
l.s = realloc(l.s, total + 1);
l.p = l.s + ofs;
l.e = l.s + total;
}

memcpy(l.p, buf, need);
l.p += need;

if (p < e) {
++p;
*l.p = 0;
return l.s;
}

p = e = buf;
}

if (l.p == l.s) {
free(l.s);
l.s = NULL;
}

return l.s;
}

int main(void)
{
char *s;

while (s = read_line(stdin)) {
puts(s);
free(s);
}

return 0;
}
------------

Normalerweise würde ich stdio nicht freiwillig für sowas benutzen, aber
das hier ist ja eine C-Gruppe.

Juergen Ilse

unread,
Aug 2, 2022, 2:16:17 PM8/2/22
to
Hallo,

Helmut Schellong <r...@schellong.biz> wrote:
> On 08/02/2022 17:30, wolfgang bauer (D) wrote:
>>
>> Das war ein Fehlalarm. Ich habe den Fehler gefunden. In einer Funktion hatte ich einen Temp.Buffer zu klein
>> angelegt. In diesen lese ich Text-Zeilen ein. Ist eine Zeile zu lang, dann knallt es eben.
>>
>> Das ist einer dieser Fehler, wie man sie hassen muss, weil sie auf die falsche Spur führen. Nach
>> dem Gesetz von Murphy enthielt die größere Testdatei "zufällig" zu lange Zeilen. Die kleinere Datei
>> nicht.
>>
>>
> Ein Puffer für unbekannte Zeilen sollte heutzutage 1 MB fassen können.
> Einfach im Stack anlegen; ist schnell und sicher.

Die korrekte Loesung lautet, darauf zu achhten, dass man nicht mehhr Zeichen
auf einmal einliest, als in den Puffer passen. Das schliesst z.B. die Ver-
wendung von gets() aus, statt dessen waere ddann z.B. fgets zu nutzen.
iDie Vergroesserung des Puffers, bis (hhoffentlich) zufaellig nichhts mehr
schief geht, ist keine Loesung sondern ein aeusserst schmutzigerr Workaround.

Tschuess,
Juergen Ilse (jue...@usenet-verwaltung.de)

Bonita Montero

unread,
Aug 2, 2022, 2:41:35 PM8/2/22
to
Am 02.08.2022 um 17:39 schrieb Helmut Schellong:

> Ein Puffer für unbekannte Zeilen sollte heutzutage 1 MB fassen können.
> Einfach im Stack anlegen; ist schnell und sicher.

Echt, was für ein unglaublicher Schwachsinn.

Thomas Koenig

unread,
Aug 2, 2022, 2:45:27 PM8/2/22
to
Rainer Weikusat <rwei...@talktalk.net> schrieb:
> Helmut Schellong <r...@schellong.biz> writes:
>> On 08/02/2022 17:30, wolfgang bauer (D) wrote:
>>>
>>>
>>> Das war ein Fehlalarm. Ich habe den Fehler gefunden. In einer Funktion hatte ich einen Temp.Buffer zu klein
>>> angelegt. In diesen lese ich Text-Zeilen ein. Ist eine Zeile zu lang, dann knallt es eben.
>>>
>>> Das ist einer dieser Fehler, wie man sie hassen muss, weil sie auf die falsche Spur führen. Nach
>>> dem Gesetz von Murphy enthielt die größere Testdatei "zufällig" zu lange Zeilen. Die kleinere Datei
>>> nicht.
>>>
>>>
>>
>> Ein Puffer für unbekannte Zeilen sollte heutzutage 1 MB fassen können.
>> Einfach im Stack anlegen; ist schnell und sicher.
>
> Einen Puffer für unbekannte Zeilen sollte man gar nicht benutzen, weder
> 1M noch in sonst einer Größe. Textdateien muß man parsen. Sogar, wenn
> man nur Zeilen heraushaben möchte. Wenn man es sich leicht machen will,
> kann man die kompletten Eingaben in den Speicher lesen und dann in place
> nach \n durchsuchen.
>
> Andernfalls kann man den nächsten Eingabeblock in einen Puffer einer
> festen Größe lesen und sich daraus Stück für Stück Zeilen variabler
> Größe in einem geeignet wachsenden Puffer zusammensetzen. ZB so:

[...]

Oder man verwendet gleich getline(), das ist wenigstens POSIX,
udn was du unten anscheinend implementierst, wobei


>#include <stdio.h>
>#include <stdlib.h>
>#include <string.h>
>
> static char *read_line(FILE *f)
> {
> static char buf[16], *p, *e;

... das hier nicht thread-safe ist (nur für den Fall, dass
das eine Rolle spielen sollte).

Rainer Weikusat

unread,
Aug 2, 2022, 2:58:26 PM8/2/22
to
fgets bringt einem hier fast nichts denn um das benutzen zu können, muß
man immer noch eine geeignete Puffergröße erraten. Bzw man muß sich einen
String ggf aus Teilstücken zusammenbauen.

Rainer Weikusat

unread,
Aug 2, 2022, 3:01:34 PM8/2/22
to
Rainer Weikusat <rwei...@talktalk.net> writes:

[...]

> static char *read_line(FILE *f)
> {
> static char buf[16], *p, *e;
> size_t nr, need, ofs, total;
>
> struct {
> char *s, *p, *e;
> } l;
>
> l.s = l.p = malloc(33);
> l.e = l.s + 32;
>
> nr = e - p;
> if (nr) {
> memcpy(l.s, p, nr);
> l.p += nr;
> }

Das hier basiert auf der Annahme, es könne kein zweites \n bereits
gelesen worden sein. Das ist natürlich falsch. Korrigierte Version:

---------
static char *read_line(FILE *f)
{
static char buf[16], *p, *e;
char *s;
size_t nr, need, ofs, total;

struct {
char *s, *p, *e;
} l;

l.s = l.p = malloc(33);
l.e = l.s + 32;

if (!p || p == e) p = e = buf;
else if (p < e) goto scan;

while (nr = fread(buf, 1, sizeof(buf), f)) {
e = buf + nr;

scan:
s = p;
while (*p != '\n' && p < e) ++p;

need = p - s;
if (need > l.e - l.p) {
ofs = l.p - l.s;

total = l.e - l.s;
do total *= 2; while (total < ofs + need);
l.s = realloc(l.s, total + 1);
l.p = l.s + ofs;
l.e = l.s + total;
}

memcpy(l.p, s, need);
l.p += need;

if (p < e) {
++p;
*l.p = 0;
return l.s;
}

p = e = buf;
}

if (l.p == l.s) {
free(l.s);
l.s = NULL;
}

return l.s;
}
----------

Die Puffergrößen sind deswegen so klein, damit alle möglichen Fälle auch
tatsächlich einmal vorkommen.

Rainer Weikusat

unread,
Aug 2, 2022, 3:11:16 PM8/2/22
to
Thomas Koenig <tko...@netcologne.de> writes:
> Rainer Weikusat <rwei...@talktalk.net> schrieb:
>> Helmut Schellong <r...@schellong.biz> writes:

[...]

>>> Ein Puffer für unbekannte Zeilen sollte heutzutage 1 MB fassen können.
>>> Einfach im Stack anlegen; ist schnell und sicher.
>>
>> Einen Puffer für unbekannte Zeilen sollte man gar nicht benutzen, weder
>> 1M noch in sonst einer Größe. Textdateien muß man parsen. Sogar, wenn
>> man nur Zeilen heraushaben möchte. Wenn man es sich leicht machen will,
>> kann man die kompletten Eingaben in den Speicher lesen und dann in place
>> nach \n durchsuchen.
>>
>> Andernfalls kann man den nächsten Eingabeblock in einen Puffer einer
>> festen Größe lesen und sich daraus Stück für Stück Zeilen variabler
>> Größe in einem geeignet wachsenden Puffer zusammensetzen. ZB so:
>
> [...]
>
> Oder man verwendet gleich getline(), das ist wenigstens POSIX,
> udn was du unten anscheinend implementierst, wobei
>
>
>>#include <stdio.h>
>>#include <stdlib.h>
>>#include <string.h>
>>
>> static char *read_line(FILE *f)
>> {
>> static char buf[16], *p, *e;
>
> ... das hier nicht thread-safe ist (nur für den Fall, dass
> das eine Rolle spielen sollte).

Mir ging's hier um zweierlei:

- bißchen Abwechslung von der reichlich hirnlosen
Dateienkopiererei und Yocto-Bedienung auf der Arbeit ;-)

- einen C-Algorithmus, der aus einer C-Datei eine Zeile
liest. Das ist nämlich gar nicht so einfach.

Wenn man das außerdem noch thread-safe machen will, muß man die
aufrufübergreifende Zustandsinformation an einem vom Aufrufer
verwalteten Ort speichern (der das dann hoffentlich thread-safe
macht).

Claus Reibenstein

unread,
Aug 2, 2022, 3:12:49 PM8/2/22
to
Rainer Weikusat schrieb am 02.08.2022 um 20:58:

> fgets bringt einem hier fast nichts denn um das benutzen zu können, muß
> man immer noch eine geeignete Puffergröße erraten.

Man muss da gar nichts erraten. Man legt einfach eine Puffergröße fest
und übergibt diese an fgets. Das ist auf jeden Fall sicher und führt
nicht zu einem Ãœberlauf, falls der Puffer mal kleiner als die
einzulesende Zeile ist. Diesen Fall kann man leicht abprüfen und
entsprechend reagieren.

> Bzw man muß sich einen
> String ggf aus Teilstücken zusammenbauen.

Zum Beispiel.

Gruß
Claus

Rainer Weikusat

unread,
Aug 2, 2022, 3:17:37 PM8/2/22
to
Claus Reibenstein <crei...@gmail.com> writes:
> Rainer Weikusat schrieb am 02.08.2022 um 20:58:
>
>> fgets bringt einem hier fast nichts denn um das benutzen zu können, muß
>> man immer noch eine geeignete Puffergröße erraten.
>
> Man muss da gar nichts erraten. Man legt einfach eine Puffergröße fest
> und übergibt diese an fgets. Das ist auf jeden Fall sicher und führt
> nicht zu einem Ãœberlauf, falls der Puffer mal kleiner als die
> einzulesende Zeile ist. Diesen Fall kann man leicht abprüfen und
> entsprechend reagieren.

Fgets ist strlcpy für stdio. Bei naiver Benutzung bekommt man anstatt
Speicherkorruption als Nebeneffekt Datenkorruption als Nebeneffekt. Das
ist nicht wirklich eine Verbesserung.

Helmut Schellong

unread,
Aug 2, 2022, 3:34:45 PM8/2/22
to
On 08/02/2022 19:23, Rainer Weikusat wrote:
> Helmut Schellong <r...@schellong.biz> writes:
>> On 08/02/2022 17:30, wolfgang bauer (D) wrote:
>>>
>>>
>>> Das war ein Fehlalarm. Ich habe den Fehler gefunden. In einer Funktion hatte ich einen Temp.Buffer zu klein
>>> angelegt. In diesen lese ich Text-Zeilen ein. Ist eine Zeile zu lang, dann knallt es eben.
>>>
>>> Das ist einer dieser Fehler, wie man sie hassen muss, weil sie auf die falsche Spur führen. Nach
>>> dem Gesetz von Murphy enthielt die größere Testdatei "zufällig" zu lange Zeilen. Die kleinere Datei
>>> nicht.
>>>
>>>
>>
>> Ein Puffer für unbekannte Zeilen sollte heutzutage 1 MB fassen können.
>> Einfach im Stack anlegen; ist schnell und sicher.
>
> Einen Puffer für unbekannte Zeilen sollte man gar nicht benutzen, weder
> 1M noch in sonst einer Größe. Textdateien muß man parsen. Sogar, wenn
> man nur Zeilen heraushaben möchte. Wenn man es sich leicht machen will,
> kann man die kompletten Eingaben in den Speicher lesen und dann in place
> nach \n durchsuchen.
>

Genau so oder ähnlich mache ich so etwas seit den 1980ern, in mindestens 5 Variationen.
Eben mit read() wiederholt den Puffer füllen, usw.
Damals hatte ich allerdings z.B. 2 KB Size gewählt, heute mindestens 16 KB bis 1 MB.

Offenbar haben hier mehrere Personen idiotische Lösungen behauptend mir zugeordnet, obwohl
mein Text oben das gar nicht hergibt!
Ich lese doch nicht über das Pufferende hinaus ein!
Entsprechenden Code hatte ich hier wiederholt gepostet.

Stefan Reuther

unread,
Aug 3, 2022, 4:17:23 AM8/3/22
to
Am 02.08.2022 um 21:17 schrieb Rainer Weikusat:
> Claus Reibenstein <crei...@gmail.com> writes:
>> Rainer Weikusat schrieb am 02.08.2022 um 20:58:
>>> fgets bringt einem hier fast nichts denn um das benutzen zu können, muß
>>> man immer noch eine geeignete Puffergröße erraten.
>>
>> Man muss da gar nichts erraten. Man legt einfach eine Puffergröße fest
>> und übergibt diese an fgets. Das ist auf jeden Fall sicher und führt
>> nicht zu einem Ãœberlauf, falls der Puffer mal kleiner als die
>> einzulesende Zeile ist. Diesen Fall kann man leicht abprüfen und
>> entsprechend reagieren.
>
> Fgets ist strlcpy für stdio.

...und es als genau das zu benutzen ist keine schlechte Idee.

Eine Alternative wäre, mit fgetc durch den Stream zu laufen.
Vermutlich(!) ist das langsamer.

Eine andere Alternative wäre, den Stream mit fread zu verarbeiten und
selbst zu parsen. Dann muss man aber alle Parse-Schritte selbst
erledigen, weil stdio keine (effiziente) Operation "diese 15 kB habe ich
zuviel gelesen, pack die mal zurück in den Puffer" gibt.


Stefan

Juergen Ilse

unread,
Aug 3, 2022, 5:22:20 AM8/3/22
to
Man kann mit fgets zumindest einen Buffoverflow vermeiden und nachh dem
einlesen testen, ob die Zeile vollstaendig ist (durch Vergleich des
Stringendes des eingelesenen Strings mit "\n"). Ist die Zeile unvoll-
staendig, kann man dann immer noch (z.B. mit realloc()) des Buffer ver-
groessern und den Rest der Zeile einlesen.

Tschuess,
Juergen Ilse (jue...@usenet-verwaltung.de)

Rainer Weikusat

unread,
Aug 3, 2022, 5:32:33 AM8/3/22
to
Stefan Reuther <stefa...@arcor.de> writes:
> Am 02.08.2022 um 21:17 schrieb Rainer Weikusat:
>> Claus Reibenstein <crei...@gmail.com> writes:
>>> Rainer Weikusat schrieb am 02.08.2022 um 20:58:
>>>> fgets bringt einem hier fast nichts denn um das benutzen zu können, muß
>>>> man immer noch eine geeignete Puffergröße erraten.
>>>
>>> Man muss da gar nichts erraten. Man legt einfach eine Puffergröße fest
>>> und übergibt diese an fgets. Das ist auf jeden Fall sicher und führt
>>> nicht zu einem Ãœberlauf, falls der Puffer mal kleiner als die
>>> einzulesende Zeile ist. Diesen Fall kann man leicht abprüfen und
>>> entsprechend reagieren.
>>
>> Fgets ist strlcpy für stdio.
>
> ...und es als genau das zu benutzen ist keine schlechte Idee.

Das ist immer eine schlechte Idee: Ein Programm, daß mit kaputten Daten
arbeitet, ist keinen Deut besser, als eines, daß kaputte Daten durch
unbeabsichtiges Ãœberschreiben von anderweitig genutzten
Speicherbereichen erzeugt. «Is aba nit gekräscht, also isses nit meine
Schuld!» ist BSD-Programmierung vom Allerfeinsten. Es soll funktionieren
und nicht bloß unaufällig Blödsinn machen.

> Eine Alternative wäre, mit fgetc durch den Stream zu laufen.
> Vermutlich(!) ist das langsamer.

Falls überhaupt dürfte das im nicht meßbaren Bereich liegen. Es ist auch
von der Logik her einfacher, als Eingaben blockweise zu verarbeiten und
entsprechend herumzuschieben. Man spart sich dadurch auch eine halbe
Kopie aller Eingabedaten.

> Eine andere Alternative wäre, den Stream mit fread zu verarbeiten und
> selbst zu parsen. Dann muss man aber alle Parse-Schritte selbst
> erledigen, weil stdio keine (effiziente) Operation "diese 15 kB habe ich
> zuviel gelesen, pack die mal zurück in den Puffer" gibt.

Die lexikalische Analyse ist dabei das kleinste Problem.

Dirk Krause

unread,
Aug 3, 2022, 7:28:00 AM8/3/22
to
Am 02.08.22 um 17:39 schrieb Helmut Schellong:
...
> Ein Puffer für unbekannte Zeilen sollte heutzutage 1 MB fassen können.
> Einfach im Stack anlegen; ist schnell und sicher.
>

Mein Vorschlag wäre, dies konfigurabel zu gestalten.
Der Puffer wird mit malloc() angefordert.
Für die Puffergröße ist im Programm ein Standardwert
voreingestellt, der im Normalfall alles abdeckt.
Ueber eine Option -- z.B. -l <Groesse> -- kann eine andere
Puffergroesse eingestellt werden. Dabei muss das Argument
natuerlich geprueft werden, um zu kleine und ggf. auch zu
grosse Werte zu vermeiden.
Wird fortlaufend mit fgets() eingelesen und ist nach erfolg-
reichem fgets() das letzte Zeichen vor dem Nullbyte kein
Newline, bricht das Programm mit der Fehlermeldung
"FEHLER: Die aktuelle Puffergroesse 16384 ist zu klein!
Bitte nutzen Sie Option -l ..., um einen groesseren
Puffer zu verwenden."
ab. Die 16384 sind hier nur ein Beispiel fuer die aktuelle
Puffergroesse.

Falls ein rewind() fuer die Datei moeglich ist, kann man auch
zwei Paesse laufen lassen. Im ersten Pass mit fgetc() einlesen
und die maximale Zeilenlaenge ermitteln, vor dem zweiten Pass
dann den Puffer halt gross genug allozieren.
Ist natuerlich nicht unproblematisch hinsichtlich
Nebenlaeufigkeit (ein anderer Prozess oder Thread kann die
Daten aendern, die im ersten Pass schon angesehen wurden).
Dies muss natuerlich gecheckt werden, z.B. ueber den
Aenderungszeitpunkt der Datei.


--
Dirk Krause

Bonita Montero

unread,
Aug 3, 2022, 8:58:43 AM8/3/22
to
Am 03.08.2022 um 10:00 schrieb Stefan Reuther:

> Eine Alternative wäre, ...

... eine moderne Sprache zu benutzen.

Helmut Schellong

unread,
Aug 3, 2022, 9:47:05 AM8/3/22
to
On 08/03/2022 13:27, Dirk Krause wrote:
> Am 02.08.22 um 17:39 schrieb Helmut Schellong:
> ...
>> Ein Puffer für unbekannte Zeilen sollte heutzutage 1 MB fassen können.
>> Einfach im Stack anlegen; ist schnell und sicher.
>>
>
> Mein Vorschlag wäre, dies konfigurabel zu gestalten.
> Der Puffer wird mit malloc() angefordert.
> Für die Puffergröße ist im Programm ein Standardwert
> voreingestellt, der im Normalfall alles abdeckt.

Meine letzte Programmierung, wo ich Dateien lese und schreibe, ist
http://www.schellong.de/htm/dragon.c.html
Zeilen 93..96, 119,120, 126, 134..146.

'buf' hat 16 KB, was in Zusammenarbeit mit Caches optimal ist.
In Zeile 142 kann 'nb' negativ sein!
read() und write() haben Vorteile gegenüber fread() und fwrite().
Der Algorithmus dürfte ziemlich optimal sein.

> Ueber eine Option -- z.B. -l <Groesse> -- kann eine andere
> Puffergroesse eingestellt werden. Dabei muss das Argument
> natuerlich geprueft werden, um zu kleine und ggf. auch zu
> grosse Werte zu vermeiden.
> Wird fortlaufend mit fgets() eingelesen und ist nach erfolg-
> reichem fgets() das letzte Zeichen vor dem Nullbyte kein
> Newline, bricht das Programm mit der Fehlermeldung
> "FEHLER: Die aktuelle Puffergroesse 16384 ist zu klein!
> Bitte nutzen Sie Option -l ..., um einen groesseren
> Puffer zu verwenden."
> ab. Die 16384 sind hier nur ein Beispiel fuer die aktuelle
> Puffergroesse.

Ich wähle eigentlich immer andere Konzepte.
Und zwar an die Aufgabe (u.a.) angepaßte Konzepte.

Wenn ich einen Puffer mit 1 MB für Zeilen verwende, ist es ein Fehler
mit Abbruch, falls bis zu dessen Ende _kein_ Newline gefunden wird!

Ich arbeite meist nicht 0-terminiert, sondern speichere Pointer
oder Positionen und jeweilige Länge.
Einen Rest kopiere ich auch nach vorne, je nach Konzept und Aufgabe.

Puffer lege ich fast immer im Stack an.
Das ist schnell und sicher, und man kann so ohne weiteres 50 MB anlegen,
was als Puffer (für read()) stets reichen dürfte.
malloc() habe ich seit 1985 nur zweimal verwendet.
Auch 'read(fd, buf+o,' kommt oft bei mir vor.

Rainer Weikusat

unread,
Aug 3, 2022, 10:13:47 AM8/3/22
to
Rainer Weikusat <rwei...@talktalk.net> writes:
> Stefan Reuther <stefa...@arcor.de> writes:

[...]

>> Eine Alternative wäre, mit fgetc durch den Stream zu laufen.
>> Vermutlich(!) ist das langsamer.
>
> Falls überhaupt dürfte das im nicht meßbaren Bereich liegen. Es ist auch
> von der Logik her einfacher, als Eingaben blockweise zu verarbeiten und
> entsprechend herumzuschieben. Man spart sich dadurch auch eine halbe
> Kopie aller Eingabedaten.

Um das mal vergleichbar zu haben:

-------
static char *read_line_c(FILE *f)
{
struct {
char *s, *p, *e;
} l;

size_t have, want;

int c;

l.s = l.p = malloc(33);
l.e = l.s + 32;

while (c = fgetc(f), c != EOF && c != '\n') {
if (l.p == l.e) {
have = l.e - l.s;
want = have * 2;
l.s = realloc(l.s, want + 1);
l.p = l.s + have;
l.e = l.s + want;
}

*l.p++ = c;
}

if (l.p > l.s) {
*l.p = 0;
} else {
free(l.s);
l.s = NULL;
}

return l.s;
}
--------

Falls man stdio benutzt (oder - angesichts der Echt Hirntoten
Modern(d)en Fadensicherheit[tm] - eine triviale Reimplementierung, die
effizieten zeichenweisen Zugriff auf einen mit gelesenen Datenblöcken
gefüllten Puffer einer festen Größe anbietet), ist es erheblich
einfacher, Zeilen zeichenweise zusammenzubauen.

Die Idee, daß man sich einen weiteren Spezialfall sparen kann, indem man
den Zeilenpuffer «heimlich» immer ein Zeichen größer macht, damit für
die Null am Ende in jedem Fall Platz ist, ist hoffentlich offensichtlich
verständlich (falls nicht, sei sie hiermit dokumentiert).

Claus Reibenstein

unread,
Aug 3, 2022, 10:20:43 AM8/3/22
to
Rainer Weikusat schrieb am 02.08.2022 um 21:17:

> Claus Reibenstein <crei...@gmail.com> writes:
>
>> Man muss da gar nichts erraten. Man legt einfach eine Puffergröße fest
>> und übergibt diese an fgets. Das ist auf jeden Fall sicher und führt
>> nicht zu einem Ãœberlauf, falls der Puffer mal kleiner als die
>> einzulesende Zeile ist. Diesen Fall kann man leicht abprüfen und
>> entsprechend reagieren.
>
> Fgets ist strlcpy für stdio. Bei naiver Benutzung bekommt man anstatt
> Speicherkorruption als Nebeneffekt Datenkorruption als Nebeneffekt.

Nur, wenn man nicht weiß, wie man mit diesen Funktionen richtig umgeht.
Bei korrekter Anwendung gibt es weder den einen noch den anderen
Nebeneffekt.

Man muss eben seine Werkzeuge beherrschen.

Gruß
Claus

Rainer Weikusat

unread,
Aug 3, 2022, 10:25:25 AM8/3/22
to
Um das auch noch dazu zu schreiben: Vorbedingung für Korrektheit ist,
daß

have * 2 + 1 <= SIZE_MAX

gilt. Andernfalls bekommt man einen Ganzahlüberlauf. Auf einem
64-Bit-System sollte das kein Problem darstellen, weil jedenfalls etwas
anderes, unerfreuliches passiert, bevor man etwas in der Gegend von
18.446.744.073.709.551.616 Bytes im Hauptspeicher untergebracht
hat. Andernfalls möglicherweise schon. Auch wenn Platz ist, sollte man
hier für Produktionscode, der mit Daten aus nicht vertrauenswürdigen
Quellen arbeitet, eine maximale Zeilengröße erzwingen, um DoS-Angriffen
zu begegnen.

Bonita Montero

unread,
Aug 3, 2022, 10:29:11 AM8/3/22
to
Am 03.08.2022 um 15:47 schrieb Helmut Schellong:

> 'buf' hat 16 KB, was in Zusammenarbeit mit Caches optimal ist.
> In Zeile 142 kann 'nb' negativ sein!

Was für ein Blödsinn. Es zählen nur die Cachezeilen, die Du anfasst.
Ob da noch Speicher über den Bedarf hinaus oder exakt nach Bedarf
allokiert wurde macht da absolut keinen Unterschied.

Rainer Weikusat

unread,
Aug 3, 2022, 11:39:27 AM8/3/22
to
Das man das machen kann, möchte ich nicht in Abrede stellen. Aber von
der Komplexität her ist das auch nicht besser, als zeichenweise zu
arbeiten und es braucht eine zusätzliche halbe Kopie (weil die Länge des
letzte gelesen/ kopierten Teil-Strings separat ermittelt werden muß).

Worauf ich hinauswollte, ist, daß fgets alleine das Problem «kopiere die
nächste Zeile in einen Puffer» nicht löst.

Claus Reibenstein

unread,
Aug 3, 2022, 3:56:59 PM8/3/22
to
Rainer Weikusat schrieb am 03.08.2022 um 17:39:

> Worauf ich hinauswollte, ist, daß fgets alleine das Problem «kopiere die
> nächste Zeile in einen Puffer» nicht löst.

Alleine nicht. Hat auch niemand behauptet.

Gruß
Claus

Rainer Weikusat

unread,
Aug 4, 2022, 10:29:57 AM8/4/22
to
Um das auch noch als vollständiges Beispiel zu haben:

--------
static char *read_line_fg(FILE *f)
{
struct {
char *s, *p, *e;
} l;

size_t len, ofs, want;

l.s = l.p = malloc(32);
l.e = l.s + 32;

while (fgets(l.p, l.e - l.p, f)) {
l.p += strlen(l.p);
if (l.p[-1] == '\n') {
l.p[-1] = 0;
return l.s;
}

ofs = l.p - l.s;
want = (l.e - l.s) * 2;
l.s = realloc(l.s, want);
l.p = l.s + ofs;
l.e = l.s + want;
}

if (l.p == l.s) {
free(l.s);
l.s = NULL;
}

return l.s;
}
-------

Das bringt einem gegenüber der zeichenbasierten Version genau nichts
außer einem zusätzlichen strlen-Aufruf.

Stefan Reuther

unread,
Aug 6, 2022, 4:51:35 PM8/6/22
to
Am 04.08.2022 um 16:29 schrieb Rainer Weikusat:
> Rainer Weikusat <rwei...@talktalk.net> writes:
>> Das man das machen kann, möchte ich nicht in Abrede stellen. Aber von
>> der Komplexität her ist das auch nicht besser, als zeichenweise zu
>> arbeiten und es braucht eine zusätzliche halbe Kopie (weil die Länge des
>> letzte gelesen/ kopierten Teil-Strings separat ermittelt werden muß).
>
> Um das auch noch als vollständiges Beispiel zu haben:
[...]
> while (fgets(l.p, l.e - l.p, f)) {
> l.p += strlen(l.p);
[...]
> Das bringt einem gegenüber der zeichenbasierten Version genau nichts
> außer einem zusätzlichen strlen-Aufruf.

Ob das Gewinn bringt, und wenn ja welchen, wäre zu messen. Das zu tun
bin ich jetzt zu faul, so relevant ist's ja am Ende nicht.

Potenzielle Gewinne liegen darin, dass z.B. fgets nur einmal pro
Zeile(nsegment) den Mutex des Streams ziehen und durch den dynamischen
Loader (PLT) springen muss.

Ersteres kann man allerdings mittels getc_unlocked() umgehen, zweiteres
ist hoffentlich bei einem anständigen dynamischen Loader wirklich kaum
messbar.


Stefan

Rainer Weikusat

unread,
Aug 7, 2022, 2:28:01 PM8/7/22
to
Stefan Reuther <stefa...@arcor.de> writes:
> Am 04.08.2022 um 16:29 schrieb Rainer Weikusat:
>> Rainer Weikusat <rwei...@talktalk.net> writes:
>>> Das man das machen kann, möchte ich nicht in Abrede stellen. Aber von
>>> der Komplexität her ist das auch nicht besser, als zeichenweise zu
>>> arbeiten und es braucht eine zusätzliche halbe Kopie (weil die Länge des
>>> letzte gelesen/ kopierten Teil-Strings separat ermittelt werden muß).
>>
>> Um das auch noch als vollständiges Beispiel zu haben:
> [...]
>> while (fgets(l.p, l.e - l.p, f)) {
>> l.p += strlen(l.p);
> [...]
>> Das bringt einem gegenüber der zeichenbasierten Version genau nichts
>> außer einem zusätzlichen strlen-Aufruf.
>
> Ob das Gewinn bringt, und wenn ja welchen, wäre zu messen. Das zu tun
> bin ich jetzt zu faul, so relevant ist's ja am Ende nicht.

Ich hatte gemeint, daß das den Algorithmus nicht einfacher macht und
nicht, daß das unter irgendwelchen Umständen vielleicht ein paar
Nanosekunden CPU- oder sogar Laufzeit einspart.

> Potenzielle Gewinne liegen darin, dass z.B. fgets nur einmal pro
> Zeile(nsegment) den Mutex des Streams ziehen und durch den dynamischen
> Loader (PLT) springen muss.
>
> Ersteres kann man allerdings mittels getc_unlocked() umgehen, zweiteres
> ist hoffentlich bei einem anständigen dynamischen Loader wirklich kaum
> messbar.

Das hirnlose Locking, das in stdio reingeflickt wurden, umgeht man am
besten dadurch, stdio gar nicht erst zu benutzen: Wenn man Threads
benutzt, denn läßt man die soweit als irgend möglich mit ihren eigenen
Speicherbereichen arbeiten, damit man Interlocking gar nicht erst
braucht.

Thomas Koenig

unread,
Aug 7, 2022, 6:09:22 PM8/7/22
to
Rainer Weikusat <rwei...@talktalk.net> schrieb:
Wäre trotzdem schön, wenn die Ausgaben bzw Eingabn nicht wild
durcheinandergewürfelt würden, wenn zwei Threads gleichzeitig
schreiben oder lesen :-) Natürlich kann man versuchen, das zu
vermeiden und nur einen Thread I/O machen zu lassen.

Rainer Weikusat

unread,
Aug 8, 2022, 10:07:12 AM8/8/22
to
Thomas Koenig <tko...@netcologne.de> writes:
> Rainer Weikusat <rwei...@talktalk.net> schrieb:

[...]

>> Das hirnlose Locking, das in stdio reingeflickt wurden, umgeht man am
>> besten dadurch, stdio gar nicht erst zu benutzen: Wenn man Threads
>> benutzt, denn läßt man die soweit als irgend möglich mit ihren eigenen
>> Speicherbereichen arbeiten, damit man Interlocking gar nicht erst
>> braucht.
>
> Wäre trotzdem schön, wenn die Ausgaben bzw Eingabn nicht wild
> durcheinandergewürfelt würden, wenn zwei Threads gleichzeitig
> schreiben oder lesen :-)

"Wäre schön, wenn man einfach so programmieren könnte, als ob es keine
Threads gäbe und sie trotzdem benutzten könnte" dürfte genau die Logik
hinter diesem "Einfall" gewesen sein. Dadurch wird das aber nicht
sinnvoll. Sinnvoll wäre es gewesen, stdio-streams als nicht threadsafe
zu deklarieren, und es Anwendungen zu überlassen, ihre Threads
entsprechend zu koordiniern, wenn die aus irgendeinem Grund gemeinsame
Streams benutzten müssen.

Auf dieser Basis kann man ein shared-everything-Modell implementieren,
wenn das einen Sinn haben sollte. Umgekehrt geht das nicht.

Thomas Koenig

unread,
Aug 8, 2022, 11:03:42 AM8/8/22
to
Rainer Weikusat <rwei...@talktalk.net> schrieb:
> Thomas Koenig <tko...@netcologne.de> writes:
>> Rainer Weikusat <rwei...@talktalk.net> schrieb:
>
> [...]
>
>>> Das hirnlose Locking, das in stdio reingeflickt wurden, umgeht man am
>>> besten dadurch, stdio gar nicht erst zu benutzen: Wenn man Threads
>>> benutzt, denn läßt man die soweit als irgend möglich mit ihren eigenen
>>> Speicherbereichen arbeiten, damit man Interlocking gar nicht erst
>>> braucht.
>>
>> Wäre trotzdem schön, wenn die Ausgaben bzw Eingabn nicht wild
>> durcheinandergewürfelt würden, wenn zwei Threads gleichzeitig
>> schreiben oder lesen :-)
>
> "Wäre schön, wenn man einfach so programmieren könnte, als ob es keine
> Threads gäbe und sie trotzdem benutzten könnte" dürfte genau die Logik
> hinter diesem "Einfall" gewesen sein. Dadurch wird das aber nicht
> sinnvoll. Sinnvoll wäre es gewesen, stdio-streams als nicht threadsafe
> zu deklarieren, und es Anwendungen zu überlassen, ihre Threads
> entsprechend zu koordiniern, wenn die aus irgendeinem Grund gemeinsame
> Streams benutzten müssen.

So kann man natürlich argumentieren.

Ein Problem ist dann, dass man keine Funktionen verwenden
kann, die irgendwo und irgendwann mal kompiliert werden (z.B.
aus Systemlibraries) und die nichts davon wissen, dass sie
unter einer Thread-Umgebung laufen. Und wenn - welche denn?
OpenMP? pthreads?

Stefan Reuther

unread,
Aug 8, 2022, 12:12:07 PM8/8/22
to
Wenn du das vermeiden willst, und dennoch mit zwei Threads auf den
gleichen File Handle (unabhängig davon, ob das ein FILE*, ein int, oder
ein HANDLE ist) schreibst, musst du dennoch selbst locken, weil nur du
weißt, wann eine Ausgabe zuende ist und die andere beginnen kann.

Das Locking, auf das `getc_unlocked()` verzichtet, ist erstmal nur
Selbstschutz von stdio. Das schützt davor, dass zwei Threads, die
parallel `putchar()` machen, abstürzen, aber nicht, dass die Ausgaben
automatisch irgendwie sinnvoll aussehen.


Stefan

Bonita Montero

unread,
Aug 9, 2022, 3:06:42 AM8/9/22
to
Ich möchte dieser Bit-Frickelei echt mal was sinnvolles entgegensetzen,
denn das geht echt nicht an, dass man heute noch so programmiert weil
man so in C zu leicht Fehler macht. Mir wird bei solchen Murks echt
recht unwohl.
Ich hab hier mal ein C++20-Beispiel das zeigt, wie ich eine Datei ein-
lese und die Zeilen dieser Datei in einen vector<> von string_views
packt. Ein string_view gibt es seit C++17 und das ist im Prinzip nur
ein Objekt das einen Start- und Ende-Pointer auf Zeichen hat. Der Sinn
hinter dieser Klasse ist, dass man damit alles machen kann was man auch
mit einem string machen kann, aber dass dieses Objekt eben keinen exter-
nen Speicher allokiert.
Der folgende Code liest einfach eine ganze Datei in einen string ein,
also einfach fortlaufenden Speicher. Dann habe ich ein Lambda, also
eine Funktion in einer Funktion, das diese Datei Zeilen-weise durchparst
(Unix, Windows und MacOS Classic Zeilenumbrüche) und jede erkannte Zeile
an das Funktions-Objekt weiterreicht das ich dem Lambda als Template
-Parameter übergebe. Da diese Parse-funktion für jeden Typ von Funktions
-Objekt neu kompiliert wird ist das im Endeffekt so, als hätte ich in C
den Code zweimal geschrieben, einmal zum Durchzählen der Zeilen, einmal
zum Füllen des Vektors mit den string_views.
Bei eigentlich allen Compilern wird das Lambda direkt dort hinkompiliert
wo es aufgerufen wird, und der Code des Funktionsobjekt wird inline in
diesen Code reinkompiliert - also keinen Unterschied zur händischen Lö-
sung in C, aber massiv weniger Aufwand.
Ich nutze keine Pointer, sondern ausschließlich Iteratoren, und das hat
den Vorteil, dass wenn ich beim Debug-Code halt das Iterator-Debugging
anstelle ich Buffer-Overflows leicht finde.
Am Ende hab ich mir nochmal den Spaß gemacht, den Vektor am Ende nochmal
zu sortieren, und das auf alle Prozessor-Kerne parallelisiert.
Ich denke das Ganze ist in keiner Sprache bei der Code-Länge gleicher-
maßen effizient realisierbar. Eine 140MB unsortierte Wortliste aus dem
Cache packt meine CPU (TR3990X) in ca. 0,21 Sekunden.

#include <iostream>
#include <fstream>
#include <string_view>
#include <vector>
#include <algorithm>
#include <execution>

#if defined(_MSC_VER)
#pragma warning(disable: 6319)
#endif
#if defined(__llvm__)
#pragma clang diagnostic ignored "-Wunused-value"
#endif

using namespace std;

int main( int argc, char **argv )
{
try
{
if( argc < 2 )
return EXIT_FAILURE;
ifstream ifs;
ifs.exceptions( ifstream::failbit | ifstream::badbit );
ifs.open( argv[1], ifstream::binary );
ifs.seekg( 0, ifstream::end );
streampos size = ifs.tellg();
if( size > (size_t)-1 )
return EXIT_FAILURE;
ifs.seekg( 0, ifstream::beg );
string data( (size_t)size, '\0' );
ifs.read( (char *)data.data(), data.size() );
using str_cit = typename string::const_iterator;
auto linify = [&]<typename Writer>( Writer writer )
requires requires( Writer writer, str_cit cit ) { { writer(
cit, cit ) }; }
{
str_cit scn = data.begin(), lineBegin, lineEnd;
auto wwriter = [&]() { lineBegin != lineEnd && (writer(
lineBegin, lineEnd ), true); };
for( ; ; )
{
lineBegin = scn;
for( ; ; )
if( scn == data.cend() )
return;
else if( *scn == '\r' )
{
lineEnd = scn;
if( ++scn == data.cend() )
{
wwriter();
return;
}
scn += *scn == '\n';
wwriter();
break;
}
else if( *scn == '\n' )
{
lineEnd = scn++;
wwriter();
break;
}
else
++scn;
}
};
size_t nLines = 0;
linify( [&]( str_cit lineBegin, str_cit lineEnd ) { ++nLines; } );
vector<string_view> views;
views.reserve( nLines );
linify( [&]( str_cit lineBegin, str_cit lineEnd ) {
views.emplace_back( lineBegin, lineEnd ); } );
sort( execution::parallel_policy(), views.begin(), views.end() );
if( argc >= 3 )
for( string_view const &view : views )
cout << view << endl;
}
catch( exception const &exc )
{
cout << exc.what() << endl;
}
}

Thomas Koenig

unread,
Aug 9, 2022, 3:55:49 AM8/9/22
to
Bonita Montero <Bonita....@gmail.com> schrieb:
> Ich möchte dieser Bit-Frickelei echt mal was sinnvolles entgegensetzen,
> denn das geht echt nicht an, dass man heute noch so programmiert weil
> man so in C zu leicht Fehler macht. Mir wird bei solchen Murks echt
> recht unwohl.
> Ich hab hier mal ein C++20-Beispiel das zeigt, wie ich eine Datei ein-
> lese und die Zeilen dieser Datei in einen vector<> von string_views
> packt.

Und ich habe hier ein Perl-Beispiel, was sowas auch macht, aber im
Gegensatz zu deinen 85 Zeilen in einer einzigen. Genauso relevant
wie dein Post.

Hier isser:

chomp (my @lines = <>);

Bonita Montero

unread,
Aug 9, 2022, 6:28:04 AM8/9/22
to
Dann lass das mal auf besagtes 140MB-File los, da kannste aber lange
warten. Sicher mindestens Faktor 1.000.

Thomas Koenig

unread,
Aug 9, 2022, 2:50:18 PM8/9/22
to
Bonita Montero <Bonita....@gmail.com> schrieb:
Ein Faktor 1.000 kann das schon sein, korrekt.

Juergen Ilse

unread,
Aug 9, 2022, 3:31:35 PM8/9/22
to
Hallo,

Bonita Montero <Bonita....@gmail.com> wrote:
> Ich möchte dieser Bit-Frickelei echt mal was sinnvolles entgegensetzen,
> denn das geht echt nicht an, dass man heute noch so programmiert weil
> man so in C zu leicht Fehler macht. Mir wird bei solchen Murks echt
> recht unwohl.
> Ich hab hier mal ein C++20-Beispiel das

... hier OffTopic ist. Wie oft muss man dir das noch mitteilen, bis du
es endlichh begreifst? Nein C++ ist nichht "das bessere C" sondern eine
*andere* Sprache. Und C++ ist hier OffTopic.

Tschuess,
Juergen Ilse (jue...@usenet-verwaltung.de)

Claus Reibenstein

unread,
Aug 9, 2022, 4:18:00 PM8/9/22
to
Kann es sein, dass Du 1.000 (= 1000) mit 1,000 (= 1) verwechselst?

Gruß
Claus

Thomas Koenig

unread,
Aug 9, 2022, 5:19:34 PM8/9/22
to
Claus Reibenstein <crei...@gmail.com> schrieb:
Wir sind hier in de.comp.lang.c , da is 1.000 = 1 :-)

Enrik Berkhan

unread,
Aug 9, 2022, 6:33:05 PM8/9/22
to
Und nicht zu vergessen:

1,000 == 0

Bonita Montero

unread,
Aug 9, 2022, 9:37:41 PM8/9/22
to
Vollidiot.

Bonita Montero

unread,
Aug 9, 2022, 10:18:55 PM8/9/22
to
Nein, 1,000 == 0 ergibt 1.

Juergen Ilse

unread,
Aug 10, 2022, 12:19:22â