Robert Bienert: String concat in c++

Beitrag lesen

Hi Martin!

OK, gebongt – float- und double-Werte sind insofern sogar recht praktisch, weil ich da in gewissen Grenzen den Platzverbrauch selbst vorgeben kann. Aber: Wieviele Stellen muss ich denn schlimmstenfalls für einen 64-Bit-Wert vorsehen ...

Moment... 2^32 ist ungefähr 4E+09, 2^64 also rund 16E+18, normalisiert 1.6E+19 oder ±8E+18, also schlimmstenfalls 20 Stellen.

bzw. welcher Programmierer weiß so etwas?

Jeder, der sich nicht mehr "Anfänger" nennen muss. ;-)

Genau da haben wir in den meisten Fällen leider das Problem.

[...] in anderen Fällen gibt es strlen()
Leider fehlte dies im angesprochenen Beispiel. Ich weiß aus eigener Erfahrung, dass man sich auch in C mit dem Prinzip Hoffnung oft durchmogeln kann

Ja, gesundes Gottvertrauen (oder Gelassenheit durch Unwissenheit) ist auch eine Methode, und wie du schon andeutest, treten solche Fehler nur in einem Bruchteil der Fälle wirklich gravierend zutage. Aber mir wäre nicht wohl dabei...

Das sollte nicht nur dir und mir so gehen, sondern jedem Programmierer. Dann würde zwar der heise-security-Newsticker einschlafen, aber die Anwender könnten ruhiger schlafen und müssten nicht ständig Patches einspielen oder hoffen, dass kein Hacker die aktuelle Lücke ausnutzt.

oder die Möglichkeit, %s mit einer Längenangabe zu versehen.
Zu welchem Standard ist dann denn kompatibel?

Standard? Keine Ahnung, frag mich nicht solche Sachen. Mein Compiler (bzw. seine RTL) versteht das jedenfalls und setzt es korrekt um. Und darauf kommt es letzten Endes an.

Darauf kommt es _dir_ letzten Endes an. Ich denke da anders, weil meine Programme sehr häufig auf einer anderen Plattform entwickelt werden, als sie später laufen werden. Dank des GCC schreibe ich mittlerweile sogar Code auf dem Mac und kompiliere die .exe für den Kunden auf meiner alten DOSe. Mit so einer Kompatibilität erspare ich mir auch die Scherereien mit den WinAPI-Änderungen neuer Windows-Versionen oder falls der Kunde auf ein anderes System wechselt.

Hmm. Ich bin da wohl aus anderem Holz geschnitzt. Ich liebe die Freiheit, die C mir bietet, auch wenn sie gewisse Risiken birgt.
Die muss man erst einmal erblicken und dann unter Kontrolle bekommen.

Die Freiheit oder die Risiken?

Man muss die Risiken erkennen können und dann eine Möglichkeit sehen, diese unter Kontrolle zu bekommen. Oder anders ausgedrückt: Man muss lernen, mit seiner Freiheit umzugehen.

Wie gesagt, die Freiheit weiß ich zu schätzen, und der Risiken bin ich mir größtenteils bewusst, weil ich maschinennah denke und intuitiv den zu meiner C-Anweisung gehörenden Assembler-Code vor Augen habe.

Warum programmierst du dann in C und nicht in Assembler?

Oft geht mir sogar diese Freiheit nicht weit genug, so dass ich mich über Beschränkungen der Sprache an sich oder eines bestimmten Compilers ärgere (Stichwort Typkompatibilität).
Wie soll ich das denn interpretieren?

Beispiele:

Warning: Mixing Pointers to signed and unsigned char (mir doch egal, ich mach doch keine Arithmetik damit!)

Und was passiert beim Dereferenzieren? Du vergleichst einen signed mit einem unsigned char, also einen Datentyp mit Wertebereich [-128,127] mit einem Datentyp des Wertebereiches [0,255].

DWORD n;
  if (n==-1)
   { ... }
Warning: Constant out of range in comparison  (Scheißcompiler! Du sollst nicht denken, sondern machen!)

Du kannst doch die Warnungen deaktivieren. Diese Meldung ist gut, es gibt Compiler, die behandeln -1 bei einem DWORD als UINT32MAX-1, also eine sehr große Zahl.

BYTE  *b;
DWORD *d;
  d = b;
Error: Type mismatch  (Herrje! Pointer ist pointer! Was ich damit mache, ist doch mein Problem!)

Wieso willst du so etwas machen, abgesehen davon, dass BYTE und DWORD unterschiedliche Größen haben? In diesem Beispiel zeigt d nach der Zuweisung nicht nur auf b, sondern auch noch auf die 3 Byte dahinter. Was willst du damit anfangen?

Noch mehr Beispiele?

Besser nicht.

[…] Am besten ist es, die Denkweisen und Paradigmen, die man von der einen Programmiersprache kennt, vollkommen über Bord zu werfen und sich in die Sprache einzudenken, mit der man gerade arbeitet.

Nein, das kann ich nicht. Für mich ist Assembler die Königin aller Programmiersprachen (denn nur da habe ich _wirklich_ alles selbst in der Hand), und alles andere muss sich bei mir daran messen. Da kommt C zwar schon recht gut ran, aber manchmal eben nicht nah genug.

OK, ich würde Assembler als die Mutter aller Sprachen bezeichnen, weil jeder Compile-Vorgang, jede virtuelle Maschine später bei einer Assembler-Art landet, und natürlich habe ich deshalb alles unter Kontrolle, aber wofür? In den meisten Fällen kann man meiner Meinung nach auf die Hersteller von Betriebssystemen, Bibliotheken und Programmiersprachen vertrauen, dass sie keinen groben Schrott anbieten.
Mit diesem Argument würde ich übrigens nie Windows benutzen, weil es viel zu viele Seiteneffekte hat, die man nicht in die Hand nehmen kann. Gentoo dürfte das richtige Linux für dich sein.

Das kann ich nicht bestätigen. Ich kann mittlerweile recht gut C++-Programmieren und ich finde, dass gerade die OOP (in C++) zusammen mit der STL gute Helfer bei der Vermeidung unnötiger Sicherheitslücken, Speicherlecks, … sind.

Ja, aber gerade OOP neigt auch dazu, riesige Mengen Maschinencode zu produzieren, obwohl man das gleiche in klassischer prozeduraler Programmierung mit einem Bruchteil an Code hätte realisieren können.

Soweit ich weiß, wurde OOP auch nicht erfunden, um mit weniger Maschinencode performantere (und buntere) Programme erstellen zu können, sondern aus praktischen Überlegungen wie Wiederverwendbarkeit, Modularisierung, Wartbarkeit, Portabilität.

Dazu kommt, dass meiner Ansicht nach gerade durch die Kapselung bei der OOP der Überblick über die Zusammenhänge verlorengeht - ist jedenfalls mein persönlicher Eindruck.

Das soll er ja auch in gewisser Weise. Schau dir z.B. mal das Stream-Konzept von C++ an: Unabhängig davon, mit was für einem Ausgabe-„Gerät“ (bzw. Eingabe-) ich es zu tun habe (Dateien, Strings, Pipes), die Schnittstelle ist immer gleich; außerdem kann man auf diesem Konzept aufbauend weitere Klassen hinzudefinieren, die analog funktionieren, z.B. Sockets, Datenbanken oder XML-Dokumente. Der Vorteil ist eine konsistente API, in der es vollkommen gleichgültig ist, woher meine Daten kommen und wohin sie wandern. Wenn ich in meinem Programm später auf ein anderes „Gerät“ umsteige, brauche ich nur wenige Code-Zeilen zu ändern.

Was ich z.B. zur Stringbehandlung sagte, gilt auch für dynamisch veränderliche Datenstrukturen: Das alles ist mit C++ deutlich sauberer, unkomplizierter und sicherer machbar.

Hmmm. Mir ist deutlich wohler, wenn ich Sachen wie die Speicherverwaltung den dafür zuständigen Betriebssystemfunktionen überlasse, anstatt irgendwelche C++-Konstruktionen zu verwenden, bei denen kaum jemand wirklich im Detail erklären kann, wie sie funktionieren.

Das Rad wird auch mit C++ keiner neu erfunden haben, von daher kannst du davon ausgehen, dass auch die C++-Allokatoren malloc und Co verwenden werden. Außerdem sind mit den Techniken von C++ Optimierungen in der Speicherverwaltung möglich, die man in C selbst nie entwickeln würde, weil es einfach viel zu viel Aufwand wäre.

Und was die Schlampigkeit bei C++ angeht: Der Punkt, der mich am meisten stört, wenn ich fremden Code lesen darf, ist das Deklarieren von neuen Variablen mitten im Programmcode. Reines C zwingt mich wenigstens noch dazu, alle Deklarationen am Anfang eines Blocks vorzunehmen, anstatt Kraut-und-Rüben-artig überall da, wo es mir gerade gefällt.

Der Vorteil daran, dass alles eine Anweisung ist, taucht z.B. dann auf, wenn du mit großen Objekten arbeitest, von denen du erst innerhalb der Funktion (also nicht schon Anfang) weißt, ob du sie brauchst oder nicht. Aber wie auch sonst kannst du nicht das schlampige Kodieren hauptsächlich der Programmiersprache anlasten. Mit Assembler kann man wahrscheinlich eher nen Obfuscated-…-Contest gewinnen als mit C.

while ((dp = readdir(dirp)) != NULL)
    /* write out the files in this directory */
    printf(dp->d_name);


>   
> Auaaa!  
>   
> > Das ganze wird natürlich bei %n im Dateinamen spaßig.  
>   
> Hä? Das wird bei fast allen Kombinationen mit '%' spaßig. Aber wofür steht %n?  
  
Naja, mit sämtlichen %-Kombinationen gibt es nur einen harmlosen Speicherzugriffsfehler. Mit %n kannst du in die korrespondierende Integervariable schreiben, wieviele Elemente printf bislang bearbeitet hat:  
  
~~~c
  
char *name;  
int elem;  
  
printf("Hallo %s, %lu Sekunden seit 1.1.1970\n%n", name, time(NULL), &elem);  
  
/* elem enthält nun den Wert 2. */  

D.h. mit %n schreibst du auf dem Stack! Bei dem schicken Speicherlayout der Intel-CPUs bzw. diverser Betriebssysteme ist damit (fast) alles möglich, z.B. Code zur Ausführung vorlegen oder Rücksprungadressen umbiegen.

Viele Grüße,
Robert