Code-Schnipsel
Moderatoren: broesel, Martin Conrad, PatrickThema: substr() & Co.
substr() & Co.
broesel (webmaster) am 27.10.2008 um 17:14
/* analog zu substr() aus PHP, reserviert entsprechend Speicher */
char *substr(const char *s, size_t start, size_t len)
{
char *d;
assert(s != NULL);
d = malloc(len + 1);
if(d == NULL)
return NULL;
strncpy(d, s + start, len);
d[len] = '\0';
return d;
}
/* zählt die Vorkommen eines Teilstrings */
size_t substr_count(const char *haystack, const char *needle)
{
size_t len, c = 0;
assert(haystack != NULL);
assert(needle != NULL);
if(*haystack && *needle) {
len = strlen(needle);
while((haystack = strstr(haystack, needle)))
++c, haystack += len;
}
return c;
}
/* berechnet den benötigten Speicherplatz, um im String "haystack"
den Teilstring "needle" gegen "replace" zu ersetzen */
size_t substr_replace_compute_size(const char *haystack,
const char *needle,
const char *replace)
{
size_t m;
assert(haystack != NULL);
assert(needle != NULL);
assert(replace != NULL);
m = substr_count(haystack, needle);
return strlen(haystack) - m * strlen(needle) + m * strlen(replace);
}
/* liefert die Startposition von "needle" in "haystack" und speichert
sie in pos. Liefert 0 bei Erfolg, -1 bei Fehler */
int substr_index(size_t *pos, const char *haystack, const char *needle)
{
char *p;
assert(pos != NULL);
assert(haystack != NULL);
assert(needle != NULL);
*pos = 0;
p = strstr(haystack, needle);
if(p == NULL)
return -1;
while(haystack != p)
++haystack, ++(*pos);
return 0;
}
/* Ersetzt in "haystack" alle Vorkommen von "needle" durch "replace"
und speichert das Ergebnis an der Speicherstelle, auf die "s" zeigt */
void substr_replace_r( char *s,
const char *haystack,
const char *needle,
const char *replace)
{
size_t pos, len_n, len_r;
assert(s != NULL);
assert(haystack != NULL);
assert(needle != NULL);
assert(replace != NULL);
len_n = strlen(needle);
len_r = strlen(replace);
if(*needle) {
while(substr_index(&pos, haystack, needle) >= 0) {
strncpy(s, haystack, pos);
s += pos;
strcpy(s, replace);
s += len_r;
haystack += pos + len_n;
}
}
strcpy(s, haystack);
}
/* Wrapper für substr_replace_r(), der automatisch genügend Speicher
reserviert */
char *substr_replace(const char *haystack,
const char *needle,
const char *replace)
{
size_t len;
char *ret = NULL;
assert(haystack != NULL);
assert(needle != NULL);
assert(replace != NULL);
len = substr_replace_compute_size(haystack, needle, replace) + 1;
ret = malloc(len);
if(ret == NULL)
return NULL;
substr_replace_r(ret, haystack, needle, replace);
return ret;
}
Freue mich über Fragen, Kommentare, Vorschläge und insbesondere Verbesserungen.
Gruss,
Philip
PS: scusi71, Du hast eine PN.
--
The C Programming Quiz
- bitte Fragen einreichen :)
[EDIT] - Re: substr() & Co.
scusi71 am 29.10.2008 um 15:39
auf die PN antworte ich später, jetzt nur einige Bemerkungen zu deinem Code. Eigentlich sehe ich nur zwei Probleme
1. In der Funktion substr musst Du natürlich darauf achten, dass start < strlen(s) ist, da du Ansonsten außerhalb der gültigen Speicherbereichs liest.
2. Im strlen(haystack) - m * strlen(needle) + m * strlen(replace); kann es zu einem Überlauf kommen, dann liefert die Funktion einen Wert zurück, der dir im folgenden malloc keine Probleme macht, allerdings wirst Du später außerhalb dieses Bereichs schreiben. Im allg. sind es Fehler dieser Art wenn Du von Sicherheitsproblemen durch modifizierte Bilddateien etc. liest.
Jetzt noch ein paar Bemerkungen.
1. Du benutzt assert, das ist sehr gut, allerdings solltest Du dir überlegen ob die Funktion nicht auch einige der jetzt abgefangenen Zustände sinnvoll verarbeiten kann. Was spricht z.B. für eine Rückgabe von NULL für substr (test, NULL) oder substr (NULL, wert) bzw. substr(NULL, NULL)?
2. Funktionen die Du nur intern benötigst solltest Du "static" machen. Wenn Du dann noch in den exportierten Funktionen die Parameter überprüfst, dann kannst Du in den static Funktionen darauf im allg. verzichten.
3. Du neigst dazu alles in Funktionen zu zerlegen, allerdings bedeutet das hier, dass du unnötig viele teure strlen Aufrufe benötigst, evtl. ist es daher effizienter alles in einer Funktion abzuarbeiten und den Ergebnispuffer Schrittweise per realloc zu vergrößern bzw. alternativ die Länge der Zeichenketten per Parameter an die internen Funktionen zu übergeben.
Re: substr() & Co.
Patrick (Moderator) am 29.10.2008 um 18:46
Wie arbeitet Ihr im produktiven Einsatz mit einer Funktion wie broesels substr?
Deaktiviert ihr dann per NDEBUG alle assert-Aufrufe?
In dem Fall müsste man vor jedem substr-Aufruf deren Parameter prüfen - imho eine schlechte Lösung.
Lange Rede, kurzer Sinn: Ich verstehe nicht, was an assert so toll sein soll und würde mich freuen, wenn ihr mich aufklären würdet.
--
To follow the path: look to the master, follow the master, walk with the master, see through the master, become the master.
Re: substr() & Co.
broesel (webmaster) am 29.10.2008 um 20:27
im produktiven Einsatz werden die assert()s mit NDEBUG entfernt. Nicht alle Funktionen sind total definiert, d.h. sie liefern nur bei bestimmten Argumenten ein korrektes Ergebnis. Hintergrund ist Effizienz: warum sollte die Funktion Checks durchführen, von denen der Caller schon weiss dass sie unnötig sind? Ein gutes Beispiel ist strlen(NULL), was je nach libc auch mal zum Absturz führt.
IMO ist der Caller dafür verantwortlich, sinnvolle Argumente bereitzustellen. Eine Funktion muss keine Fehlerkonditionen testen, die sie nicht selbst erzeugt (z.B. durch ein malloc(), dass NULL liefert).
Siehe auch meine Antworten weiter unten.
Hallo scusi71,
Zitat:
1. In der Funktion substr musst Du natürlich darauf achten, dass start < strlen(s) ist, da du Ansonsten außerhalb der gültigen Speicherbereichs liest.
Das ist IMO Aufgabe des Callers. Werde den Check in ein assert() stecken.
Zitat:
2. Im strlen(haystack) - m * strlen(needle) + m * strlen(replace); kann es zu einem Überlauf kommen, dann liefert die Funktion einen Wert zurück, der dir im folgenden malloc keine Probleme macht, allerdings wirst Du später außerhalb dieses Bereichs schreiben. Im allg. sind es Fehler dieser Art wenn Du von Sicherheitsproblemen durch modifizierte Bilddateien etc. liest.
Hrrr, das stimmt leider. Mir fällt auf Anhieb nur ein, m*strlen(replace) gegen eine Schleife zu ersetzen, in der addiert wird und die dann überprüft, ob der neue Wert kleiner als der alte ist. Geht das auch schlauer?
Zitat:
1. Du benutzt assert, das ist sehr gut, allerdings solltest Du dir überlegen ob die Funktion nicht auch einige der jetzt abgefangenen Zustände sinnvoll verarbeiten kann. Was spricht z.B. für eine Rückgabe von NULL für substr (test, NULL) oder substr (NULL, wert) bzw. substr(NULL, NULL)?
Dagegen spricht, dass dann der Rückgabewert NULL mehrere Bedeutungen haben kann, nämlich "zu wenig Speicher" und "dämliche Argumente".
Zitat:
2. Funktionen die Du nur intern benötigst solltest Du "static" machen. Wenn Du dann noch in den exportierten Funktionen die Parameter überprüfst, dann kannst Du in den static Funktionen darauf im allg. verzichten.
Mache ich i.A. auch, hatte aber in diesem Fall tatsächlich vor, alle geposteten Funktionen public zu machen. Will nicht gezwungen sein, Funktionen zu benutzen die Speicher reservieren und stelle daher auch die "statischen" Funktionen zur Verfügung.
Zitat:
3. Du neigst dazu alles in Funktionen zu zerlegen, allerdings bedeutet das hier, dass du unnötig viele teure strlen Aufrufe benötigst, evtl. ist es daher effizienter alles in einer Funktion abzuarbeiten und den Ergebnispuffer Schrittweise per realloc zu vergrößern bzw. alternativ die Länge der Zeichenketten per Parameter an die internen Funktionen zu übergeben.
Hehe, das stimmt, ich zerlege gerne in Funktionen. Bin Informatiker, kein Programmierer - ich halte Wrapper für eine Tugend.
Mit den unnötigen strlen()-Aufrufen hast Du natürlich recht; meine neue Lösung sieht so aus, dass ich eine static-Funktion habe, der man alles via Argumenten übergibt; die beiden bisherigen Funktionen sind dann Wrapper dafür.
Herzlichen Dank für die Antwort, war wie immer eine Bereicherung.
Gruss,
Philip
--
The C Programming Quiz
- bitte Fragen einreichen :)
[EDIT] - Re: substr() & Co.
scusi71 am 29.10.2008 um 20:37
1. Kommt dabei langsamer Code raus
2. Gibts im allg. doch Folgefehler die im schlimmsten Fall nicht zum Absturz sondern zu falschen Ergebnissen führen.
Und mal ganz ehrlich, was ist dir lieber ein falsch ausgelegtes Bauteil im Flieger in dem Du sitzt oder ein Absturz der Software beim Entwickler dieses Fliegers? Gibt natürlich auch Bereiche indem man defensiv Programmieren muss weil man wertvolle Datenbestände verarbeiten muss etc.
Aber zurück zum assert, assert ist das Mittel der Sparche um Pre- und Postconditions in der Debug Version zu überprüfen. In der Nodebug Version sind also alle Überprüfungen raus und du verbrauchst keine Rechenzeit für diese Tests. Auf diese Weise kommst Du mit deiner Debug Version einem Problem natürlich deutlich schneller auf die Spur als wenn Du erst mal schrittweise durch Debuggen musst. Allerdings kann dieser Vorteil von assert natürlich auch ein Problem sein, wenn der Code direkt mit Benutzerdaten arbeiten muss, dann findet man den Fehler im allg. erst beim Kunden und kommt und "feste" Überprüfungen nicht herum.
Hier noch mein Vorschlag für substr()
char * substr (const char *const restrict src,
const size_t start,
const size_t len)
{
char * sub = NULL;
if (src && *src && len)
{
size_t size = strlen(src);
if (start < size)
{
size -= start;
if (len < size)
size = len;
sub = malloc (size + 1);
if (sub)
{
memcpy (sub, src + start, size);
sub[size] = '\000';
}
}
}
return sub;
}
