Forum: Compiler & IDEs StringToUpper


von Thomas (Gast)


Lesenswert?

Hallo,

7 Jahre Ing. und 7 Jahre nicht mehr programmiert ;)
Angespornt durch diesen Thread:
Beitrag "Ein sachlicher Vergleich Pascal <-> C"
Habe ich jetzt MinGW + Eclipse + CDT installiert und spiele ein wenig 
rum.

In diesem Thread wir die StringToUpper Funktion von folgender Seite sehr 
zerrissen:
http://www.bernd-leitenberger.de/pascal-und-c.shtml

Original Code:
1
char * StringToUpper(char * zeile) {
2
  char * temp;
3
  int i;
4
  int p;
5
  p = strlen(zeile);
6
  temp = (char *) malloc(p);
7
  for (i = 0; zeile[i] != 0 && i < 256; i++)
8
    if (zeile[i] >= 'a' && zeile[i] <= 'z')
9
      temp[i] = (char) zeile[i] - 32;
10
    else
11
      temp[i] = zeile[i];
12
  temp[i] = 0;
13
  return temp;
14
}
So wie ich das sehe enthält der Code einen Buffer-Overflow-Bug, weil das 
Byte für die abschließende Null nicht allokiert wurde (strlen() liefert 
nur die Länge der sichtbaren Zeichenkette zurück).

Mein Code sieht so aus:
1
char* StringToUpper1(char* txt) {
2
  char* tmp;
3
  int i;
4
  for (i = 0; txt[i] != '\0'; i++);
5
  tmp = (char*) malloc(i + 1);
6
  for (i = 0; txt[i] != '\0'; i++)
7
    if (txt[i] >= 'a' && txt[i] <= 'z')
8
      tmp[i] = txt[i] - 32;
9
    else
10
      tmp[i] = txt[i];
11
  tmp[i] = '\0';
12
  return tmp;
13
}
Also auch nicht viel anders.

Persönlich würde ich das direkt auf der original Zeichenkette machen:
1
char* StringToUpper2(char* txt) {
2
  for (int i = 0; txt[i] != '\0'; i++)
3
    if (txt[i] >= 'a' && txt[i] <= 'z')
4
      txt[i] -= 32;
5
  return txt;
6
}

Dem oben verlinktem Thread entnehme ich, dass einige es ganz anders 
machen würden.
Mich interessiert wie?

von DirkB (Gast)


Lesenswert?

In etwa so (ungetestet)
1
#include <ctype.h>
2
char* strtoupper(char* txt) {
3
char *start = txt;
4
  for(;txt && *txt;txt++)
5
    *txt = toupper(*txt);
6
  return start ;
7
}

von Dr. Sommer (Gast)


Lesenswert?

Angenommen dass die Aufgabe ist die Umwandlung ohne C-Standardfunktionen 
(außer malloc) zu machen (zu Lernzwecken) und einen neuen String zu 
allokieren:
1
#include <stddef.h>
2
#include <stdlib.h>
3
 
4
char* strToUpper (const char* src, size_t len) {
5
  char* res = (char*) malloc (len+1);
6
  for (size_t i = 0; i < len; i++) {
7
    if (src[i] >= 'a' && src[i] <= 'z')
8
      res[i] = src[i] - ('a' - 'A');
9
    else
10
      res[i] = src[i];
11
  }
12
  res[len] = 0;
13
  return res;
14
}
Die Länge von Strings sollte man immer mitschleppen als extra size_t 
-Variable, um sich ständige strlen() -Aufrufe zu sparen. Falls man die 
Länge des zu übergebenen Strings nicht kennt, dann muss man eben doch 
strlen() aufrufen wenn man diese Funktion verwenden will. Die 
for-Schleife im standardformat "for (size_t i = 0; i < len; i++)" kann 
von Compilern gut optimiert werden. Für Speicher-Indices und 
Längenangaben sollte man size_t verwenden (zB für x86_64 macht das einen 
Unterschied). Wichtig ist es in der Dokumentation der Funktion 
anzugeben, dass der Aufrufer den zurückgegebenen String wieder freigeben 
muss (mit free()). Der explizite Cast des Rückgabewerts von malloc() 
macht den Code C++-Aufwärtskompatibel. Der Funktionskopf ist außerdem 
const-Korrekt, d.h. es wird explizit angegeben dass strToUpper den 
Inhalt von 'src' nicht verändert. So kann man zB auch string-Literale 
übergeben (die ja bekanntermaßen "const" sind).

von Thomas (Gast)


Lesenswert?

Danke.

Wozu eigentlich strlen()?
Macht das etwas anderes als
1
for (i = 0; txt[i] != '\0'; i++);

size_t...
Komische Datentypen gibt es.
Auf der Uni haben wir nur mit den Standardtypen gearbeitet.
Klar kann ein String nicht kürzer als Null Zeichen sein.
Aber warum dann nicht einfach unsigned int?

Länge des Strings immer mitschleppen mag der Performance zuträglich 
sein.
Aber ohne ist es komfortabler für den Programmierer, der die Funktion 
anwenden muss.
Würden gute Compiler die oben gelistete For-Schleife bei bekannten 
Längen nicht eh wegoptimieren?

von Dr. Sommer (Gast)


Lesenswert?

Thomas schrieb:
> Wozu eigentlich strlen()?
> Macht das etwas anderes alsfor (i = 0; txt[i] != '\0'; i++);
Nein. Hier ist das sozusagen eingebaut. Aber das bedeutet auch dass man 
keine 0-Bytes im String haben kann; das hat schon viel Gram gebracht...
> size_t...
> Komische Datentypen gibt es.
> Auf der Uni haben wir nur mit den Standardtypen gearbeitet.
Das ist ein Standard-Typ. Alles was im adressierbaren Speicher sein 
kann, hat eine Größe, und dieser Typ ist genau für solche Größen.
> Aber warum dann nicht einfach unsigned int?
Weil der evtl. zu klein sein kann. zB unter x86_64 GCC ist "unsigned 
int" 32bit, während man einen 64bit-Adressraum hat, d.h. Strings können 
auch größer als "unsigned int" sein. Deswegen nimmt man size_t, das ist 
immer groß genug für die jeweilige Ziel-Plattform (64bit auf x86_64).
> Länge des Strings immer mitschleppen mag der Performance zuträglich
> sein.
Und der Funktionalität & Sicherheit, wenn man 0-Bytes im String hat.
> Aber ohne ist es komfortabler für den Programmierer, der die Funktion
> anwenden muss.
Wenn man Komfort haben will, sollte man kein C verwenden, sondern z.B. 
C++, da geht das nämlich automatisch (ohne Performance-Einbuße).
> Würden gute Compiler die oben gelistete For-Schleife bei bekannten
> Längen nicht eh wegoptimieren?
Möglich, aber das ist sie innerhalb der Funktion natürlich nicht... Wäre 
sie es, wär der Aufruf wohl eh sinnlos...

von Thomas (Gast)


Lesenswert?

Gerade getestet: Da wird nichts wegoptimiert, auch bei -O3 nicht, auch 
dann nicht, wenn der komplette Code in main() steht.

Was meinst du mit "0-Bytes im String"?
Ich denke jeder String hat genau ein 0-Byte zum Abschließen (und wenn 
nicht, dann ist das ein Bug).
Habe ich da was falsch verstanden?

von Dr. Sommer (Gast)


Lesenswert?

Thomas schrieb:
> Ich denke jeder String hat genau ein 0-Byte zum Abschließen (und wenn
> nicht, dann ist das ein Bug).
Was wenn man einen String hat der Binärdaten enthält? Wäre ziemlich 
bitter wenn die immer beim ersten 0-Byte abgeschnitten würden. 
Text-Strings zur Ausgabe für den Benutzer haben üblicherweise genau ein 
0-Byte, aber es wäre doch schön wenn die Funktionen grundsätzlich für 
alle Strings funktionieren würden...

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Dr. Sommer schrieb:
> Was wenn man einen String hat der Binärdaten enthält?

Dann ist das kein (C-)String mehr, und eine Funktion "alles in 
Großbuchstaben" macht dann wirklich keinen Sinn...

Dr. Sommer schrieb:
> aber es wäre doch schön wenn

Man nicht immer mehr erwarten würde als spezifiziert ist...

Der ganze Orginalcode ist unnötig kompliziert und fehlerbehaftet, solche 
"Vergleiche" haben aber den Vorteil das man gleich sieht, das der Autor 
zumindest eine der beiden Sprache über die er meint zu Urteilen nicht 
verstanden hat ;-)

(Trifft man übrigens nicht nur bei C vs X immer wieder an...)

von PittyJ (Gast)


Lesenswert?

>>aber es wäre doch schön wenn die Funktionen grundsätzlich für
alle Strings funktionieren würden...

Ja, und wir sind im Jahr 2013. Da gibt es auch so etwas wie Umlaute.
Und es gibt auch so etwas wie Unicode.

Von daher nehme ich diese Funktionen aus dem K&R gar nicht mehr, sondern 
verwende Klassenbibliotheken, die auch so etwas behandeln können.

von Dr. Sommer (Gast)


Lesenswert?

Läubi .. schrieb:
> Dann ist das kein (C-)String mehr, und eine Funktion "alles in
> Großbuchstaben" macht dann wirklich keinen Sinn...
Angenommen man schreibt so etwas wie ein Archivprogramm, man erhält beim 
Entpacken einen Dateinamen aus der Archivdatei der so aussieht: 
"hallo.exe\0.doc\0". Mit etwas Pech zeigt die GUI des Archivprogramms 
den kompletten Namen inklusive .doc an, aber beim Speichern der Datei 
auf die Platte wird der String aufgrund genau einer solchen 
Stringfunktion bei dem 0-Byte abgeschnitten und der User freut sich über 
eine .exe . Genau diese Art von C-Schlamperei ist für Fehler und 
Sicherheitslücken verantwortlich. Das Mit-Übergeben der Länge ist nun 
wirklich nicht schwer, und wie gesagt gibt es auch noch Sprachen die es 
richtig machen (C++ mindestens, Pascal weiß ich nicht).

Läubi .. schrieb:
> Der ganze Orginalcode ist unnötig kompliziert und fehlerbehaftet, solche
> "Vergleiche" haben aber den Vorteil das man gleich sieht,
Das stimmt wohl, aber darum gings mir nicht.

Läubi .. schrieb:
> Man nicht immer mehr erwarten würde als spezifiziert ist...
Pech wenn sich wie oben erläutert die Eingabedaten nicht an die 
Spezifikation halten :-)

PittyJ schrieb:
> Ja, und wir sind im Jahr 2013. Da gibt es auch so etwas wie Umlaute.
> Und es gibt auch so etwas wie Unicode.
Das ist natürlich auch richtig, aber darum ging es in der Übung nicht; 
eher grundlegenden Umgang mit C lernen, und dazu gehört mMn auch die 
korrekte Verarbeitung von Strings.

von Dr. Sommer (Gast)


Lesenswert?

Thomas schrieb:
> http://www.bernd-leitenberger.de/pascal-und-c.shtml
PS: Auf der Seite steht nur Unsinn, glaube kein Wort was da steht. 
Abschließend kann man aber sagen dass C alt & überholt ist und es nur 
noch sehr sehr wenig Grund gibt es überhaupt zu verwenden, und sich im 
Jahre 2013 immer noch mit zB den Schwächen der String-Verarbeitung 
herumzuplagen.

von Thomas (Gast)


Lesenswert?

Läubi schrieb:
> Der ganze Orginalcode ist unnötig kompliziert
Wie würdest du es denn machen?
Dr. Sommers und meinen Code finde ich jedenfalls ähnlich kompliziert wie 
das Original...

> und fehlerbehaftet
Findest du noch weitere Fehler außer den oben beschriebenen?

> solche "Vergleiche" haben aber den Vorteil das man gleich sieht,
> das der Autor zumindest eine der beiden Sprache über die er meint zu
> Urteilen nicht verstanden hat ;-)
Ich gehe davon aus, das der Autor in seiner Absicht ein Pamphlet gegen C 
zu verfassen diesen Code möglicherweise absichtlich vermurkst hat.
Eigentlich muss ihm ja noch nicht einmal der Bug peinlich sein, 
unterstützt er doch genau seine Argumentation.
Und so unsinnig sein Text an viele Stellen auch ist, hier hat er Recht.
Solche Bugs schleichen sich in C schnell mal ein, ich glaube da kann 
sich keiner von freisprechen.
Und das gemeine dabei ist ja, dass das Programm auch unter den meisten 
Umständen genau das tut was es soll, mitunter jahrelang...

Wir hatten im ersten Semester Pascal, und im zweiten dann C.
Von Pascal habe ich das meiste inzwischen vergessen, von C nur die 
hälfte ;)
Ich weiß nur noch, dass mir C damals wesentlich besser gefallen hat und 
ich es auch irgendwie einfacher und logischer fand.
Das lag vor allem an den ganzen Sichtbarkeiten in Pascal.
Im Nachhinein denke ich, dass mir C vielleicht auch einfach nur mehr 
Spaß gemacht hat, weil es so schön kniffelig ist mit den Zeigern zu 
jonglieren ;)

von Thomas (Gast)


Lesenswert?

Dr. Sommer schrieb:
> Abschließend kann man aber sagen dass C alt & überholt ist und es nur
> noch sehr sehr wenig Grund gibt es überhaupt zu verwenden

Naja, ich denke Betriebsysteme und Anwendungen würde man damit 
heutzutage wahrscheinlich wirklich nicht mehr schreiben, wenn man von 
vorne anfangen müsste.
Aber gerade auf uCs und DSPs ist C doch immer noch mit Abstand am 
weitesten verbreitet.
Zu Recht, würde ich annehmen. Oder wie siehst du das?

von Dr. Sommer (Gast)


Lesenswert?

Thomas schrieb:
> Aber gerade auf uCs und DSPs ist C doch immer noch mit Abstand am
> weitesten verbreitet.
Ja, leider
> Zu Recht, würde ich annehmen. Oder wie siehst du das?
Nein, zu Unrecht; C++ ist sicherer, einfacher, kompakter, eleganter, 
konsistenter, und effizienter. Der Hauptgrund warum hier noch C 
verwendet wird ist Unkenntnis und obskure Plattformen ohne verfügbare 
C++ Compiler.

von Ralf G. (ralg)


Lesenswert?

Dr. Sommer schrieb:
> Angenommen man schreibt so etwas wie ein Archivprogramm, man erhält beim
> Entpacken einen Dateinamen aus der Archivdatei der so aussieht:
> "hallo.exe\0.doc\0". Mit etwas Pech zeigt die GUI des Archivprogramms
> den kompletten Namen inklusive .doc an, aber beim Speichern der Datei
> auf die Platte wird der String aufgrund genau einer solchen
> Stringfunktion bei dem 0-Byte abgeschnitten

Vielleicht liege ich ja daneben, aber ich glaube das ist ein 
Trugschluss. Die Kombination '\0' zur Kennzeichnung der Null gibt's doch 
nur im Quelltext. Falls jetzt so eine Zeichenkette, die man von 
irgendwoher bekommt, diese Kombination enthält, wird dort auch weiterhin 
ein '\' und ein '0' stehen. Denke ich mal.

von Dr. Sommer (Gast)


Lesenswert?

Ralf G. schrieb:
> wird dort auch weiterhin
> ein '\' und ein '0' stehen. Denke ich mal.
Das meinte ich doch, der String war in C-Schreibweise. Das heißt ein 
String wo mitten drin ein 0-Byte steht. Hätte ich "hallo.exe0.doc0" 
geschrieben hätte keiner gewusst was gemeint ist...

von Ralf G. (ralg)


Lesenswert?

Dr. Sommer schrieb:
> Das heißt ein String wo mitten drin ein 0-Byte steht.

Wer macht denn sowas?????

von Dr. Sommer (Gast)


Lesenswert?

Ralf G. schrieb:
> Wer macht denn sowas?????
Wie in dem Beispiel, ein so genannter "Hacker". Oder Leute die 
Binärdaten verarbeiten zur Speicherung in Dateien oder Transfer über 
Netzwerk.

von Ralf G. (ralg)


Lesenswert?

Naja, etwas an den Haaren herbeigezogen...

Dr. Sommer schrieb:
> Wie in dem Beispiel, ein so genannter "Hacker".
Fix in Großbuchstaben umgewandelt und zack, der Hack funktioniert nicht 
mehr. Ist doch gut! ;-)

> Oder Leute die
> Binärdaten verarbeiten zur Speicherung in Dateien oder Transfer über
> Netzwerk.
Dann sind das aber keine Strings mehr. Was nützt mir eine Funktion, die 
von allen Zahlen in einen Array(!!) 32 abziehen kann?

von Dr. Sommer (Gast)


Lesenswert?

Ralf G. schrieb:
> Fix in Großbuchstaben umgewandelt und zack, der Hack funktioniert nicht
> mehr. Ist doch gut! ;-)
Doch, genau deswegen funktioniert der Hack, weil aus der "harmlosen" 
hallo.exe.doc eine hallo.exe wird. Das ist natürlich kein direktes 
Problem, kann aber eines werden...

Ralf G. schrieb:
> Dann sind das aber keine Strings mehr. Was nützt mir eine Funktion, die
> von allen Zahlen in einen Array(!!) 32 abziehen kann?
Da ist diese konkrete Funktion etwas nutzlos, ja. Aber andere 
String-Funktionen kann man gut darauf anwenden - wenn sie mit 0-Bytes 
klarkommen.

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Dr. Sommer schrieb:
> Da ist diese konkrete Funktion etwas nutzlos, ja. Aber andere
> String-Funktionen kann man gut darauf anwenden - wenn sie mit 0-Bytes
> klarkommen

Das ist doch eine akademische Diskussion... C-Strings sind so definiert, 
dass sie Null-Terminiert sind, hätte und könnte zählt da nicht. Wenn der 
"String" nicht Null-Terminiert ist, dann ist es kein C-String und es 
steht dir frei alle C-Stringfunktionen nochmal neu für deine Strings zu 
schreiben. Spätestens bei strlen wird es abstrus wenn du der Funktion 
die Länge des Strings mitgeben musst damit diese den Wert gleich wieder 
zurückliefert...

Thomas schrieb:
> Wie würdest du es denn machen?

Direkt auf dem Array arbeiten, eine Kopie ist hier unnötig und schränkt 
die Verwendung ein, wenn der Aufrufer unbedingt die 
Orginalzeichenkette behalten will, so kann er sie selber kopieren.

Das ganze läßt sich dan etwa so zusammenschrumpfen[1]:
1
void StringToUpper(char *zeile){
2
  int len = strlen(zeile);
3
  for(int i = 0; i < len; i++) {
4
    zeile[i] = toupper(zeile[i]);
5
  }
6
}
Man könnte das jetzt noch in eine einzige Zeile mit while "schrumpfen" 
und würde dann auch ohne strlen auskommen... So ist es aber erst mal 
lesbarer.

Nun sieht das natürlich nicht mehr ausreichen monströs und komplex aus 
um zu zeigen wie böse die Sprache X ist ;-)

Allgemein kann man sagen: In jeder Sprache kann ich sch...okoladen code 
schreiben wenn ich mich nur genug anstrenge oder mich zu wenig auskenne. 
Das kann man aber nicht primär der Sprache anlasten, bestenfalls kann 
eine Sprache mich unterstützen sowas nicht zu tun, verhindern aber 
seltenst.

[1] http://www.tutorialspoint.com/ansi_c/c_toupper.htm

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Läubi .. schrieb:

> Das ganze läßt sich dan etwa so zusammenschrumpfen[1]:
1
> void StringToUpper(char *zeile){
2
>   int len = strlen(zeile);
3
>   for(int i = 0; i < len; i++) {
4
>     zeile[i] = toupper(zeile[i]);
5
>   }
6
> }
7
>

Dein Aufruf von strlen() ist überflüssig :-)

Das reicht vollkommen:
1
void StringToUpper(char *zeile) {
2
  while (*zeile) {
3
    *zeile = toupper(*zeile);
4
    zeile++;
5
  }
6
}

Gruß,

Frank

von Oliver S. (oliverso)


Lesenswert?

Na, wenn schon, dann noch so:
1
void StringToUpper(char *zeile) {
2
  while (*zeile)
3
    *zeile = toupper(*zeile++);
4
}

Oliver

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Frank M. schrieb:
> Dein Aufruf von strlen() ist überflüssig :-)

Siehe mein Kommentar am ende, bitte wenigstens die Beiträge bis zu ende 
lesen:

Läubi .. schrieb:
> Man könnte das jetzt noch in eine einzige Zeile mit while "schrumpfen"
> und würde dann auch ohne strlen auskommen... So ist es aber erst mal
> lesbarer.

Oliver S. schrieb:
> Na, wenn schon, dann noch so

Wie gesagt, eine Anweisung reicht ;-)
1
void StringToUpper(char *zeile) {
2
   while(*zeile++=toupper(*zeile));
3
}
Das ist dann aber wirklich langsam abschreckend.

von Karl H. (kbuchegg)


Lesenswert?

Läubi .. schrieb:

> Wie gesagt, eine Anweisung reicht ;-)
>
1
void StringToUpper(char *zeile) {
2
>    while(*zeile++=toupper(*zeile));
3
> }
> Das ist dann aber wirklich langsam abschreckend.

Vorsicht:
Sowohl
1
   *zeile++=toupper(*zeile)
als auch
1
   *zeile = toupper(*zeile++);
haben undefiniertes Verhalten.
Der ++ muss extra sein, damit die Reihenfolge von Dereferenzierung und 
Pointer erhöhen definiert ist.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Naja, wenn's jetzt primär um's Golfen geht, ist C vielleicht nicht die 
richtige Ausrüstung dafür. So ist bspw. in Haskell der Funktionsrumpf 
noch kürzer als der Name:
1
stringToUpper = map toUpper

Und das ist kein schlechter, sondern sogar sehr guter Stil, für jeden im 
Club sofort verständlich (weil es keiner anders machen würde) und frei 
von jeglichen undefined Behaviors :)

von Dr. Sommer (Gast)


Lesenswert?

Läubi .. schrieb:
> Das ist doch eine akademische Diskussion... C-Strings sind so definiert,
> dass sie Null-Terminiert sind, hätte und könnte zählt da nicht.
Das ist natürlich praktisch. Wenn man dann doch mal Char-Arrays mit 
0-Bytes verarbeiten will muss man "nur" alle Funktionen neu schreiben 
und leicht anpassen. Und jedes mal beim Einlesen/Empfangen von "fremden" 
Strings genau überlegen ob ein bösartig eingefügtes 0-Byte nicht doch 
Schaden anrichten könnte (wie es beim PHP-Interpreter, wimre, der Fall 
war, der intern Strings immer korrekt mit Längenangabe verwendete aber 
beim Aufruf von C-Stdlib-Funktionen plötzlich gemeine Probleme bekam).
> Spätestens bei strlen wird es abstrus wenn du der Funktion
> die Länge des Strings mitgeben musst damit diese den Wert gleich wieder
> zurückliefert...
Die Idee ist natürlich strlen ganz wegzulassen und die Länge immer 
explizit zu haben. Wie es ziemlich viele Sprachen machen... Hätte man 
damals ein
1
struct String { char* data; size_t length; };
in den Standard aufgenommen wäre alles halb so schlimm gewesen, aber zu 
spät.

Hier mal ein interessanteres Beispiel über "gute" Programmiersprachen 
(C++) und Code-Wiederverwendbarkeit:
1
#include <vector>
2
#include <list>
3
#include <iostream>
4
 
5
struct People {
6
    std::string Name;
7
    int ID;
8
};
9
 
10
void test1 () {
11
    People ppl [] {{ "sam", 1},  {"john", 2}, {"mark", 3}};
12
    for (People& p : ppl)
13
        std::cout << p.Name << " " << p.ID << std::endl;
14
}
15
 
16
void test2 () {
17
    std::list<People> ppl {{ "sam", 1},  {"john", 2}, {"mark", 3}};
18
    for (People& p : ppl)
19
        std::cout << p.Name << " " << p.ID << std::endl;
20
}
21
 
22
void test3 () {
23
    std::vector<People> ppl {{ "sam", 1},  {"john", 2}, {"mark", 3}};
24
    for (People& p : ppl)
25
        std::cout << p.Name << " " << p.ID << std::endl;
26
}
27
 
28
int main () {
29
    test1 ();
30
    test2 ();
31
    test3 ();
32
}
In test1() wird ein Array aus structs angelegt, initialisiert und dann 
durchiteriert. Später entscheidet man sich um, doch lieber eine 
verkettete Liste zu verwenden, und muss nur die Typangabe auf 
std::list ändern, und der gesamte restliche Code kann genau so bleiben 
wie er ist und funktioniert immer noch (siehe test2). In test3 das 
gleiche Spiel noch einmal für einen vector, eine Art automatisch 
wachsendes Array mit dabei ammortisiert konstanter Laufzeit. Da der 
Compiler an jeder Stelle den Typ kennt und genau weiß was getan werden 
muss, ist das mindestens so effizient wie äquivalenter C-Code. Das ganze 
ist in C quasi unmöglich, Pascal weiß ich nicht, und überall verwendbar 
wo es einen GCC gibt...

von Oliver S. (oliverso)


Lesenswert?

Für jedes hätte, wäre, könnte, müsste, ist es bei C einfach ein paar 
Jahrzehnte zu spät.
Und C++ als "gute" Sprache zu bezeichnen, ist auch gewagt. Dazu gibt's 
auch da zu viele Unschönheiten.

Oliver

von Karl H. (kbuchegg)


Lesenswert?

Dr. Sommer schrieb:
> Läubi .. schrieb:
>> Das ist doch eine akademische Diskussion... C-Strings sind so definiert,
>> dass sie Null-Terminiert sind, hätte und könnte zählt da nicht.
> Das ist natürlich praktisch.

Das ist überhaupt nicht praktisch. Das ist eine reine Definitionssache.
Du kannst dir natürlich deine eigene Defintion machen, was für dich ein 
String sein soll, nur nenne es dann nicht C-String. Denn damit ist 
gegenseitiges aneinander vorbeireden vorprogrammiert.

> Wenn man dann doch mal Char-Arrays mit
> 0-Bytes verarbeiten will muss man "nur" alle Funktionen neu schreiben
> und leicht anpassen.

Ja und?
In C ist definiert, was ein C-String ist und welche Eigenschaften er 
hat. Diese definierten Eigenschaften schliessen das enthalten sein eines 
0 Bytes aus.


> explizit zu haben. Wie es ziemlich viele Sprachen machen... Hätte man
> damals ein
1
struct String { char* data; size_t length; };
in den
> Standard aufgenommen wäre alles halb so schlimm gewesen, aber zu spät.

Es hat alles seine Vor und Nachteile.
So schleppe ich einen size_t mit, der auch Speicher verbraucht.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Dr. Sommer schrieb:
> Wenn man dann doch mal Char-Arrays mit 0-Bytes verarbeiten will muss man
> "nur" alle Funktionen neu schreiben und leicht anpassen.

Wo ist das Problem? Char-Arrays, die beliebige Daten enthalten, sind 
halt keine Strings. Punkt. Die C-Runtime-Library enthält diverse 
Funktionen zum Verarbeiten von Strings.

Wenn man irgendwelche eigenen Datentypen definiert, dann muss man 
Funktionen, die damit irgendwas anstellen sollen, tatsächlich anpassen 
oder gar neu schreiben.

Deine "string"-Struktur übrigens ist auch nicht besonders praktisch; was 
ist, wenn so ein String verkürzt oder verlängert werden soll? 
Informationen über den maximal zur Verfügung stehenden Speicher sind 
nicht enthalten, wie also würdest Du das in C angehen?

Zeig mal musterhaft, wie Du strncat implementieren würdest.

von Dr. Sommer (Gast)


Lesenswert?

Oliver S. schrieb:
> Für jedes hätte, wäre, könnte, müsste, ist es bei C einfach ein paar
> Jahrzehnte zu spät.
Wenn das wenigstens allgemein akzeptiert würde und man C in Friede ruhen 
lassen würde...
> Und C++ als "gute" Sprache zu bezeichnen, ist auch gewagt. Dazu gibt's
> auch da zu viele Unschönheiten.
Das stimmt. Aber C++ ist die eleganteste Sprache in der Effizienzklasse 
der klassischen maschinennahen Sprachen inkl. C, Pascal etc. Und dank 
der letzten Neuerungen kann man auf viele Altlasten verzichten...

Karl Heinz Buchegger schrieb:
> Das ist überhaupt nicht praktisch. Das ist eine reine Definitionssache.
Ich vergaß die Leidenschaftslosigkeit der C-Programmierer "Hauptsache es 
läuft irgendwie, wurst wie hässlich, fehleranfällig, unwartbar es ist". 
Du hast natürlich recht, aber das ganze ist ein Grund C nicht zu 
verwenden...

Karl Heinz Buchegger schrieb:
> Es hat alles seine Vor und Nachteile.
> So schleppe ich einen size_t mit, der auch Speicher verbraucht.
Der Speicher ist typischerweise billiger als das ständige strlen() und 
überprüfen auf '0'.

von Karl H. (kbuchegg)


Lesenswert?

Dr. Sommer schrieb:

> wachsendes Array mit dabei ammortisiert konstanter Laufzeit. Da der
> Compiler an jeder Stelle den Typ kennt und genau weiß was getan werden
> muss, ist das mindestens so effizient wie äquivalenter C-Code. Das ganze
> ist in C quasi unmöglich, Pascal weiß ich nicht, und überall verwendbar
> wo es einen GCC gibt...

Du rennst hier bei vielen offene Türen ein.
Nur die wenigsten werden dem Argument, dass C++ bessere Möglichkeiten 
als C zur Verfügung hat, etwas wirklich großartiges entgegensetzen. Vor 
allen Dingen dann nicht, wenn sie selber C++ programmieren.

Aber das ändert nichts daran, was ein C-String ist und wie er definiert 
ist. Wenn es in der Aufgabenstellung um C-Strings geht, dann geht es um 
C-Strings. Und da ist dann deine private String-Definition ganz einfach 
fehl am Platze.

von Karl H. (kbuchegg)


Lesenswert?

Dr. Sommer schrieb:

>> So schleppe ich einen size_t mit, der auch Speicher verbraucht.
> Der Speicher ist typischerweise billiger als das ständige strlen() und
> überprüfen auf '0'.

Ähm.
90% der C-String-Arbeit kommt gänzlich ohne strlen aus.
Bernd Leitenberger in dem anfangs verlinkten Artikel (und um den geht es 
ja im weitesten Sinne immer noch) ist das krasse Beispiel eines 
Programmierers, der eben nicht String Aufgaben mit Pointer löst, obwohl 
dies problemlos möglich wäre. Er benutzt eben nicht das typische C-Idiom 
um etwas mit jedem Character eines Strings zu machen, sondern erfindet 
sein eigenes umständliches Süppchen und kreidet das dann der Sprache an.
Er arbeitet in seinem C Programm nicht mit der C Systematik der String 
Behandlung sondern gegen sie.

von Karl H. (kbuchegg)


Lesenswert?

Und nur damit das nicht in den falschen Hals kommt:

Ja, C-Strings sind nicht das Gelbe vom Ei.
Ja, in C gibt es einige Themenkreise, die ein 'Pain in the ass' sind, um 
es freundlich auszudrücken. Das wird wohl keiner abstreiten oder schön 
reden wollen.

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Karl Heinz Buchegger schrieb:
> haben undefiniertes Verhalten

Deshalb hab ich ja auch die Korrekte und lesbare Variante vorgeschlagen, 
danke für den Hinweis, da ich solche "Tricks" nicht nutze ist diese 
ganze Thematik nicht 100% präsent im Kopf :-)

von Dr. Sommer (Gast)


Lesenswert?

Rufus Τ. Firefly schrieb:
> Deine "string"-Struktur übrigens ist auch nicht besonders praktisch; was
> ist, wenn so ein String verkürzt oder verlängert werden soll?
Mir ist das völlig klar, diese Struktur hätte mehr einen psychologischen 
Effekt: Es gäbe einen einheitlichen Weg Strings mit Länge zwischen 
verschiedenen Programmen&Libraries auszutauschen, und würde die 
Implementation von strncat & Friends vereinfachen, aber nicht obsolet 
machen.
> Zeig mal musterhaft, wie Du strncat implementieren würdest.
Jetzt schreibe ich schon wieder hässliches C, wenns denn sein muss:
1
#include <stddef.h>
2
#include <stdio.h>
3
#include <stdlib.h>
4
5
#define LSTR(x) { x, sizeof(x)-1 }
6
7
typedef struct {
8
    char* data;
9
    size_t length;
10
} String;
11
 
12
String myCat (const String a, const String b) {
13
    size_t len = a.length+b.length;
14
    String res = { malloc (len), len};
15
    for (size_t i = 0; i < a.length; i++) {
16
        res.data [i] = a.data [i];
17
    }
18
    for (size_t i = 0; i < b.length; i++) {
19
        res.data [a.length+i] = b.data [i];
20
    }
21
    return res;
22
}
23
 
24
int main () {
25
    const String a = LSTR("Hel\0lo "), b = LSTR("World!\n");
26
    String res = myCat (a, b);
27
    fwrite (res.data, 1, res.length, stdout);
28
    free (res.data);
29
}
Das 0-Byte sieht man am normalen Terminal nicht, aber es wird 
übertragen, bei Umleitung in eine Datei oder ein Socket etc. ist es da. 
Ich muss mir leider mit einem ekligen Makro zur Definition des 
Literal-"String" behelfen, da C keine templates hat.

Karl Heinz Buchegger schrieb:
> 90% der C-String-Arbeit kommt gänzlich ohne strlen aus.
Außer strcat was 90% der Stringfunktion-Aufrufe ausmacht? :D 
Programmierer aller herren Programmiersprachen sind immer so enttäuscht 
wenn "Hello" + "World" nicht geht...

Karl Heinz Buchegger schrieb:
> Er benutzt eben nicht das typische C-Idiom
Gehören die chronischen Buffer-Overflows auch zu den C-Idiomen? ;-) Ja 
ich weiß, die ist der Programmierer schuld. Aber eine Sprache kann dabei 
helfen, so etwas zu vermeiden; C tut das nicht.

Karl Heinz Buchegger schrieb:
> Das wird wohl keiner abstreiten oder schön
> reden wollen.
Sag das mal Mr. Torvalds und seiner Gefolgschaft aus renitenten 
C-Programmierern die Unmengen an geradezu gefährlichem Copypasta-C-Code 
produzieren... ;-D

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Dr. Sommer schrieb:
> Jetzt schreibe ich schon wieder hässliches C, wenns denn sein muss:

Naja, und anders als strncat kopierst Du beide Strings um.
Effizient ist das nicht.

Außerdem verwendest Du dynamische Speicherverwaltung, was strncat 
nicht macht.

Wenn schon dynamische Speicherverwaltung, warum dann nicht realloc?

von Dr. Sommer (Gast)


Lesenswert?

Rufus Τ. Firefly schrieb:
> Naja, und anders als strncat kopierst Du beide Strings um.
> Effizient ist das nicht.
Das stimmt. Könnte man anders machen. Oder std::string verwenden, da ist 
das nämlich alles schon so implementiert.

Rufus Τ. Firefly schrieb:
> Außerdem verwendest Du dynamische Speicherverwaltung, was strncat
> nicht macht.
Dafür funktioniert es für alle String-Längen und overflowt nicht 
plötzlich (wir erinnern uns, #1 oder #2 der Sicherheitslücken (vor oder 
nach SQL-Injection?))... Man könnte diese Funktion natürlich leicht 
erweitern, bei Bedarf mehr Speicher anzufordern. Das kann strncat 
nicht... Und überhaupt, strncat, das ist doch viel zu "sicher", strcat 
ist doch viel schönerer C-Style.

Rufus Τ. Firefly schrieb:
> Wenn schon dynamische Speicherverwaltung, warum dann nicht realloc?
Hatte ich nicht dran gedacht - wäre auch ungesund wenn "data" nicht das 
Ergebnis von "malloc" war.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Dr. Sommer schrieb:
> Könnte man anders machen. Oder std::string verwenden, da ist das nämlich
> alles schon so implementiert.

Wir reden von C.

> Dafür funktioniert es für alle String-Längen

Das macht strncat auch.

> und overflowt nicht plötzlich

Das macht strncat auch nicht.

> Man könnte diese Funktion natürlich leicht erweitern, bei Bedarf mehr
> Speicher anzufordern. Das kann strncat nicht...

Nö, aber das soll es ja auch nicht.

Im Embedded-Bereich, auf µCs, die teilweise nur wenige hundert Bytes RAM 
haben, möchte man weder dynamische Speicherverwaltung verwenden, noch 
irgendwelche Funktionen nutzen, die von sich aus auf die Idee kommen, 
Speicher anzufordern.

strncat aber kann auch dort sehr gut verwendet werden.

von Karl H. (kbuchegg)


Lesenswert?

Rufus Τ. Firefly schrieb:
> Dr. Sommer schrieb:
>> Könnte man anders machen. Oder std::string verwenden, da ist das nämlich
>> alles schon so implementiert.
>
> Wir reden von C.

Ich denke, genau hier liegt der springende Punkt.
Dr. Sommer möchte die Diskussion auf C++ ausdehnen. Wogegen ich 
prinzipiell nichts hätte, wenn sich dieser Thread nicht explizit auf C 
und String-Verarbeitung in C beziehen würde.

> Buffer Overflow .... so etwas zu vermeiden; C tut das nicht.

Natürlich nicht. Das war auch nie die Idee hinter C, das man ein 
Rundum-Sorglos-Paket schnürt. C war von Anfang an nach der Maxime 
entworfen: You don't get, what you didn't ask for.

von Thomas (Gast)


Lesenswert?

> Wogegen ich prinzipiell nichts hätte, wenn sich dieser Thread nicht
> explizit auf C und String-Verarbeitung in C beziehen würde.
Kein Problem.
Meine Frage ist ja geklärt.
Ich gebe hiermit den Thread für C++ Diskussionen frei ;)

von Lukas K. (carrotindustries)


Lesenswert?

Wer bei C bleiben will, aber dennoch Komfort-Strings haben will, der 
kann sich ja der GLib (nicht glibc) bedienen, da ist dies uvm. 
implementiert. Mit GObject hat's sogar OOP.

von Dr. Sommer (Gast)


Lesenswert?

Rufus Τ. Firefly schrieb:
> Wir reden von C.
Für das es wie gesagt kaum einen Grund gibt es zu verwenden.

Rufus Τ. Firefly schrieb:
> Das macht strncat auch.
Rufus Τ. Firefly schrieb:
>> und overflowt nicht plötzlich
>
> Das macht strncat auch nicht.
1
#include <string.h>
2
#include <stdio.h>
3
#include <stdlib.h>
4
 
5
int main () {
6
        char a [8] = "Hello";
7
        strncat (a, " cruel C-World!", 15);
8
 
9
        puts (a);
10
}
Interessant, wie funktioniert der Overflow-Schutz zB hier?

Rufus Τ. Firefly schrieb:
> Im Embedded-Bereich, auf µCs, die teilweise nur wenige hundert Bytes RAM
> haben, möchte man weder dynamische Speicherverwaltung verwenden, noch
> irgendwelche Funktionen nutzen, die von sich aus auf die Idee kommen,
> Speicher anzufordern.
Das stimmt sogar. Aber auch hier kann ein Overflow unschöne Dinge 
anrichten, weswegen man die Längen überprüfen sollte, wofür man sie 
überhaupt erst einmal braucht, wärend man die maximale Kapazität eines 
gegebenen C-Strings (=char*) überhaupt nicht ermitteln kann.

Karl Heinz Buchegger schrieb:
> Wogegen ich
> prinzipiell nichts hätte, wenn sich dieser Thread nicht explizit auf C
> und String-Verarbeitung in C beziehen würde.
Dabei eignet sich das so schön die Vorteile von C++ zu zeigen...

Karl Heinz Buchegger schrieb:
> Natürlich nicht. Das war auch nie die Idee hinter C, das man ein
> Rundum-Sorglos-Paket schnürt.
Blöderweise fangen immer noch viele mit C an, erwarten eine Sorglospaket 
und fallen auf die Nase, bzw lassen den Kunden auf die Nase fallen.
Ich finde es daher legitim die Nachteile von C aufzuzeigen um die Leute 
zu ermutigen etwas besseres zu verwenden.

Lukas K. schrieb:
> Wer bei C bleiben will, aber dennoch Komfort-Strings haben will, der
> kann sich ja der GLib (nicht glibc) bedienen, da ist dies uvm.
> implementiert. Mit GObject hat's sogar OOP.
Das ist großartig, weil man C++ "irgendwie nicht mag" alles in C 
nachzubauen. Das "OOP" in GObject ist ausschließlich dazu da, um ein 
generisches OOP-Interface für viele Sprachen, wie Scriptsprachen, 
bereitzustellen. Das ist nicht dazu gedacht, um damit in C 
OOP-Anwendungsentwicklung zu betreiben. Wer es dennoch macht und C++ 
ablehnt braucht eine Jacke die hinten zugeknöpft wird...

von Karl H. (kbuchegg)


Lesenswert?

Dr. Sommer schrieb:

> Karl Heinz Buchegger schrieb:
>> Natürlich nicht. Das war auch nie die Idee hinter C, das man ein
>> Rundum-Sorglos-Paket schnürt.
> Blöderweise fangen immer noch viele mit C an, erwarten eine Sorglospaket
> und fallen auf die Nase, bzw lassen den Kunden auf die Nase fallen.

Da bin ich wiederrum bei dir.
Wobei ich das sogar noch ausweiten würde.
Blöderweise lernen mittlerweile viele ihre Programmiersprache nicht mehr 
ordentlich und systematisch, EHE sie sich damit in Projekte wagen. Man 
kann schon mit C arbeiten - aber man muss halt eben auch viel rundherum 
wissen und vorher geübt haben.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Dr. Sommer schrieb:
> Interessant, wie funktioniert der Overflow-Schutz zB hier?

Wenn Du strncat falsch aufrufst, dann funktioniert er nicht.

Ich denke, daß es müßig ist, mit Dir weiterzudiskutieren.

von Fred S. (kogitsune)


Lesenswert?

>> Das macht strncat auch nicht.
>
1
#include <string.h>
2
> #include <stdio.h>
3
> #include <stdlib.h>
4
> 
5
> int main () {
6
>         char a [8] = "Hello";
7
>         strncat (a, " cruel C-World!", 15);
8
> 
9
>         puts (a);
10
> }
Interessant, wie funktioniert der Overflow-Schutz zB hier?
Du versuchst, "Fehler" in C zu hereinzuinterpretieren, die in C++ 
vielleicht/bestimmt [ich habe keine Ahnung von C++] solche wären, es in 
C aber einfach nicht sind, denn irgendwo steht zu strncat etwas wie 
"the user needs to provide sufficient space in dest for the result".

von Ralf G. (ralg)


Lesenswert?

Ist das nicht der Vorteil von C, dass die paar Befehle und die ganzen 
Bibliotheksfunktionen ohne Netz und doppelten Boden zur Verfügung 
gestellt werden? Die Sicherheit reinzubringen, ist Sache des 
Programmierers. Sonst kann man ja bei BASIC bleiben.

von Karl H. (kbuchegg)


Lesenswert?

Fred S. schrieb:

> Du versuchst, "Fehler" in C zu hereinzuinterpretieren, die in C++
> vielleicht/bestimmt [ich habe keine Ahnung von C++] solche wären

Der springende Punkt ist, dass sich die std::string Klasse selbst intern 
um so banale Dinge wie Speicherverwaltung kümmert. D.h. dort hat man 
damit nichts zu tun
1
int main()
2
{
3
  std::string a( "Hello" );
4
5
  a += " cruel C-World!";
6
}

ist ja auch sinnvoll, wenn man C++ programmiert und man sich dynamische 
Speicherverwaltung erlauben kann. Gerade letzteren Luxus hat man halt 
auf einem µC nicht immer.

Die ganze Diskussion ist dahingehend müssig. C++ hat einen anderne 
Ansatz als C und ist ein ganzes Stück jünger. Es macht keinen Sinn ein 
heutiges Auto mit einem Ford Model T zu vergleichen. Beim Mdel T hat man 
selbst noch händisch den Scheibenwischer betätigt wenn es regnet. 
Heutige Autos haben eine Automatik, die Regentropfen auf der Scheibe 
erkennt und selbsttätig den Scheibenwischer aktiviert, so dass sich der 
Fahrer darum nicht mehr kümmern muss.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.