strlcat und sizeof

Schnelle objektorientierte, kompilierende Programmiersprache.
Antworten
GnuRip
Beiträge: 8
Registriert: Do Jul 23, 2015 1:32 pm

strlcat und sizeof

Beitrag von GnuRip » Di Okt 18, 2016 3:42 pm

Hallo zusammen, ich melde mich nach viel zu langer Zeit zurück, und habe direkt eine Frage. :oops:

Ich habe in einem kleinen Projekt folgende Funktion gefunden:

Code: Alles auswählen

void MakeName (const char *txt, char *res)
{
	Uint16 i;
	for (i=0;i<strlen(RESOURCES);i++)
	{
		res[i] = RESOURCES[i];
	}
	for (i=strlen(RESOURCES);i<=strlen(RESOURCES)+strlen(txt);i++)
	{
		res[i] = txt[i-strlen(RESOURCES)];
	}
}
Das sieht mir recht umständlich aus, das muss doch mit boardmitteln gehen.
Ich habe mir dann strcpy und strcat angeguckt, um dann zu lesen man solle lieber strlcpy und strlcat verwenden.
Jetzt verstehe ich aber den dritten Parameter von strlcpy und strlcat nicht.

In der Doku steht
The strlcpy() function copies up to size - 1 characters from the NUL-terminated string src to dst, NUL-terminating NULterminating
terminating the result.
und als Beispiel

Code: Alles auswählen

strlcpy(buf, s, sizeof(buf));
strlcat(buf, p, sizeof(buf));
Ich hätte ja gedacht, es hätte sizeof(s) bzw. sizeof(p) heißen müssen.
Außerdem gibt mir sizeof(res) immer 8 zurück.
Dadurch habe ich jetzt das Problem, dass das Ergebnis nicht vollständig ist.

Also ich habe jetzt folgenden Code:

Code: Alles auswählen

const char *RESOURCES = "";
char temp[256];

MakeName("arial.ttf", temp, RESOURCES);

void MakeName (const char *txt, char *res, const char *path)
{
	strlcpy(res, path, sizeof(res));
	strlcat(res, txt, sizeof(res));
	printf("Read::MakeName - '%s' - '%s'\n", txt, res);
}
Ausgegeben wird mir

Code: Alles auswählen

Read::MakeName - 'arial.ttf' - 'arial.t'
Wenn ich das sizeof(res) durch 256 ersetze funktioniert es, aber das scheint ja nicht richtig zu sein?

Wo ist mein Denkfehler?

Benutzeravatar
cloidnerux
Moderator
Beiträge: 3123
Registriert: Fr Sep 26, 2008 4:37 pm
Wohnort: Ram (Gibts wirklich)

Re: strlcat und sizeof

Beitrag von cloidnerux » Di Okt 18, 2016 4:04 pm

Ich hätte ja gedacht, es hätte sizeof(s) bzw. sizeof(p) heißen müssen.
Das Problem mit strcpy oder strcat ist, dass diese sich darauf verlassen, dass dein String ein "NUL" Character am Ende hat. In C werden strings mit diesem Character abgeschlossen, um sich einen separaten Parameter mit der Länge des Strings zu sparen. Hat deinen String jetzt keinen solchen terminierenden NUL-Character, wird strcat/strcpy so lange daten von src nach dst kopieren, bis das Programm abstürzt.
Ein anderes Problem ist, wenn dein src String größer ist als der dst Speicherbereich. Dann überschreibst du nämlich Daten, die im Ram hinter dem dst Speicherbereich liegen, was zu Fehlfunktion oder Absturz führen kann. Alternativ ist das ein beliebtes Angriffsziel für Sicherheitslücken.

Daher auch die Angabe über die Größe deines Zielspeicherbereichs und nicht der Länge der zu kopierenden Daten. Es soll ja ein ungewolltes Überschreiben verhindert werden und nicht ein ungewolltes lesen.

sizeof(res) gibt immer 8 zurück, weil sizeof genau das zurückgeben soll. Sizeof ist keine Funktion, sondern ein Schlüsselwort von C/C++. sizeof weist den Compiler an, den Ausdruck "sizeof([...])" durch die Größe(Anzahl Bytes) des angegeben Datentyp/Variable zu ersetzen.
sizeof(char[100]) ist 100, weil es ein Array aus 100 chars(1 byte) ist. sizeof(char*) ist 8 Byte(oder 4 Byte auf 32-Bit Systemen), weil das die Größe eines Pointers auf einen char ist!

In deinem Fall hast du zwei Optionen:
Entweder du kannst garantieren, dass die Strings path und txt NUL-Terminiert sind und daher einfach strcpy und strcat verwenden
oder
du erweiterst deine Funktion um einen zusätzlichen Parameter "bufferLength"(oder ähnlich), der die Länge des Buffers "res" angibt. Abgeschwächt könntest du auch eine Konstante/define mit der länge des Buffers anlegen, ist aber dann nicht so universell.
Redundanz macht wiederholen unnötig.
quod erat expectandum

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8859
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: strlcat und sizeof

Beitrag von Xin » Di Okt 18, 2016 8:34 pm

cloidnerux hat geschrieben:
Ich hätte ja gedacht, es hätte sizeof(s) bzw. sizeof(p) heißen müssen.
Das Problem mit strcpy oder strcat ist, dass diese sich darauf verlassen, dass dein String ein "NUL" Character am Ende hat.
Das finde ich so ungeschickt ausgedrückt. Ein String (STRcpy) hat einen NUL-Charakter am Ende zu haben, sonst ist es kein String. Möchte man einen Speicherblock kopieren, sollte man memcpy nehmen. strcpy nutzt eben genau das aus, dass ein String nur am Ende einen String besitzt, aber keine Längeninformationen.
cloidnerux hat geschrieben:du erweiterst deine Funktion um einen zusätzlichen Parameter "bufferLength"(oder ähnlich), der die Länge des Buffers "res" angibt
Das ist in meinen Augen die einzige saubere Lösung.



Ansonsten bitte die For-Schleifen ändern:

Code: Alles auswählen

for( Uint16 i = 0, n = strlen( RESOURCES ); i < n; i++ )
  ...
Resources hat eine festgelegte, während der Funktion wohl unveränderte Länge. Die sollte man einmal bestimmen und nicht bei jeden Schleifendurchlauf.
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.

Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.

GnuRip
Beiträge: 8
Registriert: Do Jul 23, 2015 1:32 pm

Re: strlcat und sizeof

Beitrag von GnuRip » Mi Okt 19, 2016 8:22 am

Danke euch, das hilft schonmal weiter.
cloidnerux hat geschrieben: Entweder du kannst garantieren, dass die Strings path und txt NUL-Terminiert sind und daher einfach strcpy und strcat verwenden
Woher weiß man denn ob ein "String" NUL-Terminiert ist?

Code: Alles auswählen

MakeName("arial.ttf", temp, RESOURCES);
Ist an der Stelle das "arial.ttf" automatisch NUL-Terminiert oder muss ich da noch etwas machen?

cloidnerux hat geschrieben: du erweiterst deine Funktion um einen zusätzlichen Parameter "bufferLength"(oder ähnlich), der die Länge des Buffers "res" angibt
Wenn ich deine Ausführungen richtig verstanden habe, heißt das auch dass es keine Möglichkeit gibt die Länge des Buffers über eine Funktion rauszufinden oder?


Wie findet ihr denn die Lösung mit den zwei Schleifen (mal abgesehen vom strlen Aufruf bei jedem Durchlauf den Xin erwähnte)? Ist das in C eine gute Lösung?

Benutzeravatar
cloidnerux
Moderator
Beiträge: 3123
Registriert: Fr Sep 26, 2008 4:37 pm
Wohnort: Ram (Gibts wirklich)

Re: strlcat und sizeof

Beitrag von cloidnerux » Mi Okt 19, 2016 8:38 am

Woher weiß man denn ob ein "String" NUL-Terminiert ist?
Alle die du im Code schreibst sind NUL Terminiert, die String Funktionen hängen das NUL Byte auch für dich an.
Ansonsten kann man Speicherbereiche einfach komplett mit NUL initialisieren, dann ist das auch kein großes Problem
Wenn ich deine Ausführungen richtig verstanden habe, heißt das auch dass es keine Möglichkeit gibt die Länge des Buffers über eine Funktion rauszufinden oder?
Nein. Der Pointer zeigt auf einen Speicherbereich, aber ob dieser jetzt 1 Byte groß ist oder 1GB, dass kannst du an dieser stelle nicht herausfinden.
Wie findet ihr denn die Lösung mit den zwei Schleifen (mal abgesehen vom strlen Aufruf bei jedem Durchlauf den Xin erwähnte)? Ist das in C eine gute Lösung?
strcpy oder strcat machen nichts anderes, von daher ist das schon ok. Es empfiehlt sich aber, die fertigen Funktionen zu nutzen. So ist zum einen klar, was du machen möchtest(einen neuen String zusammen bauen). Zum anderen kannst du sicher gehen, dass du keinen Fehler an dieser stelle machst.
Redundanz macht wiederholen unnötig.
quod erat expectandum

Antworten