Hallo Zusammen, ich stehe gerade vor einem kleinen c Problem. Definiert ist ein char* array[31]. Befüllt wird das ganze über eine HTML Seite die die Eingabe per AJAX und JSON Objekt an meinen NodeMCU weitergibt. Die maximale Eingabelänge habe ich auf Eingabeseite per JQuery auf 30 Zeichen begrenzt. Um sicherzustellen, dass ich keine Überschreitung der Länge in meiner Variable array bekomme, würde ich dies gerne prüfen und gegebenfalls den überschüssigen Rest abschneiden. Tante Google hat strncpy ausgespuckt. Hier ist aber das Problem, dass '\0' nicht automatisch angefügt wird. Gibt es eine c Standardfunktion die beides erledigt, zum einen String beliebiger Länge wenn er <=30 Zeichen ist korrekt kopiert und die '\0' Terminierung einfügt bwz. wenn es mehr als 30 Zeichen sind, die ersten 30 Zeichen nimmt sie in die Variable kopiert und '\0' terminiert? 30 Zeichen ist natürlich eine willkürliche Länge. Ich habe verschiede char* Variablen mit unterschiedlichen Längen, Für alle gilt, ich möchte sicherstellen, dass ich keinen Überlauf bekomme. Danke für alle Tipps. Gruß Frank P.S. c ist nicht meine Leib und Magen Sprache. Für einen c Fachmann mag die Frage lächerlich sein. Ich muss mir die Sachen aber teilweise mühsam im Netz zusammen suchen.
Schreib Dir einfach eine:
1 | char * mystrncpy (char * t, char * s, int l) |
2 | {
|
3 | char * r = strncpy (t, s, l); |
4 | *(t + l - 1) = '\0'; |
5 | return r; |
6 | }
|
P.S. Kommt natürlich drauf an, wie Du mystrncpy() dann aufrufen würdest. Mit l = 30 oder l = 31? Wenn 30, dann:
1 | *(t + l) = '\0'; |
Für den strncpy()-Aufruf ändert sich nichts. Das letzte Statement würde im oberen Fall das letzte Zeichen wieder überschreiben, im unteren Fall das '\0' anhängen.
:
Bearbeitet durch Moderator
Frank L. schrieb: > Gibt es eine c Standardfunktion die beides erledigt, Nein. strlcpy gibt es unter Windows und BSD. Schreib sie dir selbst.
Hallo Frank, hatte ich bzw. habe ich mir geschrieben allerdings nicht so elegant wie Deine Funktion. Ich dachte es ging einfacher. Ich bin jetzt doch noch auf strlcpy gestoßen, die meine Anforderung voll erfüllt. Ist allerdings wohl keine Standard C Funktion. Gruß Frank
Frank L. schrieb: > Tante Google hat strncpy ausgespuckt. Hier ist aber das Problem, dass > '\0' nicht automatisch angefügt wird. Was spricht dagegen, nach dem Aufruf von strncpy grundsätzlich das letzte Element des Zielstrings zu nullen? Grüßle Volker
Frank L. schrieb: > Ich bin jetzt doch noch auf strlcpy gestoßen, die meine Anforderung voll > erfüllt. Ist allerdings wohl keine Standard C Funktion. Das ist das Problem, siehe auch: https://stackoverflow.com/questions/2114896/why-are-strlcpy-and-strlcat-considered-insecure?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa Ausschnitt: "strlcpy() and strlcat() are not standard, neither ISO C nor POSIX. So, their use in portable programs is impossible. In fact, strlcat() has two different variants: the Solaris implementation is different from the others for edge cases involving length 0. This makes it even less useful than otherwise." Wenn Du damit leben kannst, okay. Bei einer selbst geschriebenen Funktion weisst Du immer, was sie tut. Obwohl ich auch kein großer Freund von strncpy() bin. In den meisten Fällen schreibt sie viel zu viel '\0' in den Target-Buffer, in den seltenen Fällen, wo es auf die letzte Position im String ankommt, versagt sie.
Ohne Aufwand ganz simpel: char d[100], *s; strncpy(d, s, sizeof(d))[sizeof(d) - 1] = '\0';
Volker B. schrieb: > Was spricht dagegen, nach dem Aufruf von strncpy grundsätzlich das > letzte Element des Zielstrings zu nullen? Wenn dann vorher. Danach ist zu spät, da ist der Bufferoverflow eventuell schon passiert. Besser strncpy_s (bzw. strcpy_s) verwenden: http://en.cppreference.com/w/c/string/byte/strncpy http://en.cppreference.com/w/c/string/byte/strcpy
Irgendwer schrieb: > Volker B. schrieb: >> Was spricht dagegen, nach dem Aufruf von strncpy grundsätzlich das >> letzte Element des Zielstrings zu nullen? > Wenn dann vorher. Danach ist zu spät, da ist der Bufferoverflow > eventuell schon passiert. Hä? Wieso sollte strncpy einen Bufferoverflow erzeugen, wenn die Manpage das Gegenteil behauptet? ...und wenn Du vor Aufruf von strncpy den String nullst, überschreibt Dir strncpy Dein letzes Nullbyte wieder. Also ein super vorschlag... :-( Grüßle Volker
Manche programmieren lieber defensiv: get_the_input(*quelle,*ziel) { char c; c = quelle[30]; quelle[30] = NULL; strcpy(ziel,quelle); quelle[30] = c; } /* Entweder wird ziel-String = quelle-String (mit jeweils abschließender NULL), oder ziel-String auf 30 Chars vom quelle-String (+ NULL-Char) begrenzt. quelle bleibt unverändert! FERTIG. - Wäre natürlich toll, wenn quelle[] auch mindestens für 31 chars definiert wurde... ;-) */
Defender schrieb: > Manche programmieren lieber defensiv: > > get_the_input(*quelle,*ziel) Hier fehlen die Typen von quelle und ziel. Unschön: Mit NULL meint man eigentlich was anderes als '\0' - allgemein auch als NUL (mit einem L) bezeichnet. Außerdem modifizierst Du hier die Quelle, was unter Umständen unerwünschte Nebeneffekte haben kann. Das geht zum Beispiel bei Stringkonstanten komplett in die Hose.
:
Bearbeitet durch Moderator
Defender schrieb: > FERTIG Compiler error so bald Quelle nicht-modifizierbar, also const, ust, zudem nicht multithreadtauglich.
Es gibt noch eine scheinbar fragile Lösung: array[30]=0; setzen (bzw. lassen) Und alle weiteren strncpy mit 30. Fragile deshalb, weil du konsequent nur n-1 Elemente beschreiben darfst. Scheinbar, weil Du auch sonst nicht mehr Speicher beschreiben darfst als vorgesehen.
Nochmal etwas grundsätzliches zu strncpy Mir scheint, die Intention ist eher, n bytes des Ziels zu füllen, egal wie lang (bzw. kurz) der String ist. Irgendwie ist die Vorstellung "ich habe hier einen String, und den kann ich mitten im Satz irgendwo abbrechen, ohne beim Ziel Konfusion zu erzeugen" eher neueren Datums. Eine angemessene Reaktion auf einen zu langen String (strlen!) wäre eine Fehlermeldung (in diesem Fall den Abweis der Eingabe) bzw. zumindest die Einfügung von special-Characters am Ende, z.B. "}" oder "..." oder " (string gekürzt weil das Original zu lang war und das Ziel nicht mehr als %i Spei(string gekürzt weil das Original zu lang war, -+#oö#345+90upj"
Frank L. schrieb: > Tante Google hat strncpy ausgespuckt. Hier ist aber das Problem, dass > '\0' nicht automatisch angefügt wird.
1 | char d[100] = {0}; // initialisierung like memset((void*) d, 0, sizeof(d)); |
2 | char *s; |
3 | strncpy(d, s, sizeof(d)-1); |
:
Bearbeitet durch User
Von meinem Verständnis her gibt es mit strncpy kein Problem: Die Manpage sagt, strncpy kopiert n Bytes. Ist die Quelle kürzer als n Bytes, wird der Rest im Ziel mit Null Bytes aufgefüllt. Wenn man sein Array dann mit x + 1 anlegt, das Array einmal mit Null initialisiert und bei strncpy immer mit x arbeitet, wird es nie einen nicht terminierten String geben:
1 | #define TEXTLENGTH 100
|
2 | char text [ TEXTLENGTH + 1]; |
3 | text[TEXTLENGTH] = '\0'; |
4 | strncpy(text, "123", TEXTLENGTH); |
Einziger Nachteil ist das setzen von NULL für das restliche Array. Damit kann man aber im Normalfall leben.
DanVet schrieb: > Da es noch keiner genannt hat > snprintf() oder direkt sprintf(array, ".30s", quelle);
Achim S. schrieb: > oder direkt sprintf(array, ".30s", quelle); Danach steht in array der String ".30s" und nicht der Inhalt von quelle.
Nun warum unbedingt die Standard Bibliothek bemühen? Die 30 Zeichnen kann man viel schlanker in einer einfachen FOR Schleife kopieren.
Frank M. schrieb: > Danach steht in array der String ".30s" und nicht der Inhalt von quelle. Nun, man könnte den Formatstring noch um das fehlende Prozentzeichen erweitern ...
>
1 | > char d[100] = {0}; // initialisierung like memset((void*) d, 0, |
2 | >
|
Ist es denn nicht möglich, dem Compiler beizubringen dass nach initialisierung mit NUL, NIEMALS je wieder auf das letzte Zeichen geschrieben wedeb darf? Irgendwas mit const Plus struct und/oder union... Es muss ja nicht immer bloss bei elementarsten Datentypen bleiben. (ja, in C++ besteht die Möglichkeit Datenattribute von Klassen ausschließlich mit deren Methoden zu bearbeiten)
Fragomat schrieb: > Es muss ja nicht immer bloss bei elementarsten Datentypen bleiben. > > (ja, in C++ besteht die Möglichkeit Datenattribute von Klassen > ausschließlich mit deren Methoden zu bearbeiten) Ja klar, du kannst dir ganz umständlich ein Struct bauen ;-) struct mystruct { data : char[100]; terminator : char; } __attribute__((packed)); und den terminator fest auf 0 setzen ... Von mir aus ein Sicherheitsnetz ... trotzdem würde sowas niemand jemals machen.
:
Bearbeitet durch User
Marco H. schrieb: > Die 30 Zeichnen kann man viel schlanker in einer einfachen FOR Schleife kopieren oder memcpy und 0 am Ende. Fragomat schrieb: > Ist es denn nicht möglich, dem Compiler beizubringen dass nach > initialisierung mit NUL, NIEMALS je wieder auf das letzte Zeichen > geschrieben wedeb darf? Oder Du setzt es selber jedesmal, wenn Du angst hast. Am besten mit struct wie Mampf F. schrieb:
Andreas E. schrieb: > Von meinem Verständnis her gibt es mit strncpy kein Problem: Gibt es auch nicht. Allerdings machst du dir eins: > Wenn man sein Array dann mit x + 1 anlegt, das Array einmal mit Null > initialisiert und bei strncpy immer mit x arbeitet, wird es nie einen > nicht terminierten String geben: Doch, in dem Moment, in dem eine andere Funktion an einer anderen Stelle im Programm versehentlich das letzte '\0' überschreibt. Daher IMMER brav NACH jedem Einschreiben neuer Daten die abschließende '\0' selber schreiben und die mehrfachen "Vorschläge" das Ziel-Array vorher mit '\0' zu initialisieren ignorieren.
Seit C11 (also schon recht lange) gibt es strncpy_s(), was auch aufwandstechnisch wegen fehlendem padding vorzuziehen ist.
:
Bearbeitet durch User
Die ganze Sache hat aber mehrere Schönheitsfehler. Das Json im der Response ist ein String an sich, die Terminierung ist dabei Wurst. Da die Länge durch den Emfangsbuffer und die Playload fest steht. Das Objekt mit dem Type String darf keine Sonderzeichen enthalten :"{}[] usw. oder sie müssen entwertet werden. Dann wird aus ::: %:%:%: die 30 Zeichen wären Makulatur. Wenn du das Json per JavaScript erzeugst wird das vermutlich im Hintergrund so in das Objekt geschrieben. Die Länge des Objekts und der Typ ist bekannt ! schon daraus kann man beim parsen prüfen ob die Eingabe korrekt ist bzw. das Json. Das Objekt ist nicht terminiert ! "ObjektName": "String" , maximal das Ende des Json muss aber nicht sein wegen der Payloadlänge. Es reicht also eine Copy Funktion die das Entwertungszeichen herausfiltert und das Ende des Strings im Zielarray Terminiert. Die Funktion kopiert maximal nur 30 Zeichen in das Zielarray.
:
Bearbeitet durch User
Die Standardfunktionen von C zum kopieren von Strings sind leider wenig durchdacht. Ich benutze eine eigene Funktion, die strcpy, strncpy und strcat optimal ersetzt.
1 | char* stpcpylim(char *t, const char *s,const char * lim){ |
2 | |
3 | lim--; |
4 | while(*s && t<lim) *t++=*s++; |
5 | *t=0; |
6 | return t; |
7 | }
|
Wie stpcpy gibt sie das Stringende (Zeiger auf die '\0') zurück statt dem eh schon bekannten Stringanfang. Dadurch kann man auch mehrere Strings hintereinander kopieren, ohne den Anfangsstring immer wieder nach der \0 abscannen zu müssen. Wenn man am Ende die Länge des Strings braucht, um sie in eine Datei zu schreiben, kann man sie mit einer simplen Subtraktion berechnen. lim ist ein Zeiger auf das erste Byte, dass nicht beschrieben werden darf, in den meisten Fällen buf+sizeof(buf). Der Vorteil des Grenzpointers statt einer Längenangabe ist, dass er über alle Aktionen auf dem Puffer gleich bleibt und nicht für jedes Anhängen neu berechnet werden muss. Da ich die Funktion im Quelltext habe, ist sie natürlich auch portabel.
:
Bearbeitet durch User
Jobst Q. schrieb: > lim ist ein Zeiger auf das erste Byte, dass nicht beschrieben werden > darf Und warum beschreibst Du es u.U. trotzdem?
Jobst Q. schrieb: > Der Vorteil des > Grenzpointers statt einer Längenangabe ist, dass er über alle Aktionen > auf dem Puffer gleich bleibt und nicht für jedes Anhängen neu berechnet > werden muss. Find ich gut, Deine Funktion. Wäre nie darauf gekommen, dass bei strcpy so zu tun. Danke.
Fragender schrieb: > Und warum beschreibst Du es u.U. trotzdem? tut er normalerweise nicht. - lim-- vorher - UND die Abfrage auf < (statt kleiner gleich) - UND das fehlende increment bei *t = 0; t bei Rückgabe ist also maximal lim-1 und enthält die 0 (für alle Fälle, in denen t<lim bei übergabe ist). Vermutlich meinst Du den Fehlerfall, wenn t >= lim ist. Dann wäre "nichts tun" vermutlich ebenso fatal wie "den String sofort terminieren".
Beitrag #5422733 wurde vom Autor gelöscht.
Mampf F. schrieb: > Fragomat schrieb: >> Es muss ja nicht immer bloss bei elementarsten Datentypen bleiben. >> > > Ja klar, du kannst dir ganz umständlich ein Struct bauen ;-) > > struct mystruct { > data : char[100]; > terminator : char; > } __attribute__((packed)); > > und den terminator fest auf 0 setzen ... > > Von mir aus ein Sicherheitsnetz ... trotzdem würde sowas niemand jemals > machen. Genau, so nicht weil der entscheidende Kniff noch fehlt: terminator muss noch unveränderbar auf NUL initialisiert sein und der Compiler soll darüber wachen. Geht dasda? (sorry, kein CComp aufm Handy)
1 | struct mystruct { |
2 | data : char[100]; |
3 | terminator : const char = 0; |
4 | } __attribute__((packed)); |
Fragomat schrieb: > struct mystruct { > data : char[100]; > terminator : const char = 0; > } __attribute__((packed)); Ups, war das von mir? Sorry, ich code in zuvielen unterschiedlichen Sprachen gleichzeitig xD
1 | struct mystruct { |
2 | char data[100]; |
3 | const char terminator; |
4 | } __attribute__((packed)); |
Hmm, ich glaube direkt im Struct initialisieren geht nicht ... Die Frage ist, was enthält dann "terminator"
:
Bearbeitet durch User
Mampf F. schrieb: > struct mystruct { > char data[100]; > const char terminator; > } __attribute__((packed)); Welchen Vorteil soll die gepackte Struktur gegenüber data[101] haben? Sie schützt genausowenig vor einem Buffer-Overflow (und damit Überschreiben des Platzes, an welchem der finale Terminator steht) wie das stinknormale Array mit Größe N+1. Ich sehe nur den Nachteil einer erhöhten Komplexität. Außerdem frage ich mich, ob der Compiler sich überhaupt an das packed-Attribute halten muss oder ob das vom Compiler lediglich als eine Empfehlung angesehen wird. Wenn nur Empfehlung: Wie sehen die "Zwischenräume" einer Struct aus? Sind die garantiert 0?
:
Bearbeitet durch Moderator
Achim S. schrieb: > Vermutlich meinst Du den Fehlerfall, wenn t >= lim ist. Dann wäre > "nichts tun" vermutlich ebenso fatal wie "den String sofort > terminieren". Ja, da haben Funktionen mit Längenangaben einen kleinen Vorteil, mit t_size als Typ erlauben sie keine negativen Längen. Aber zum Glück werden Parameter ja normalerweise nicht mit Zufallszahlen bestückt. Wenn man lim mit buf + sizeof(buf) berechnet, ist schon mal sichergestellt, dass lim größer als buf ist. Solange ausschließlich stpcpylim t verändern kann, kann t auch nicht größer als lim-1 werden. Will man dazwischen etwas wie
1 | *t++=','; |
ausführen, braucht es natürlich einen Vergleich von t mit lim als Bedingung:
1 | if(t+2 < lim) *t++=','; |
Oder man ersetzt es gleich konsistent durch
1 | t=stpcpylim(t,",",lim); |
:
Bearbeitet durch User
Jobst Q. schrieb: > Ja, da haben Funktionen mit Längenangaben einen kleinen Vorteil, mit > t_size als Typ erlauben sie keine negativen Längen. Tschuldigung, es ist Dein Code, aber das ist Quatsch ;-) Bei fehlerhafter Länge (also <= 0, also auch 0 bei size_t) oder fehlerhafter Grenze (also lim<=t) musst Du zwischen Pest und Cholera wählen: - Entweder den String trotzdem Nullen (das tut Dein Code) - Oder nichts tun (z.B. so)
1 | char* stpcpylim(char *t, const char *s,const char * lim) |
2 | {
|
3 | if(t<lim--) |
4 | {
|
5 | while(*s && t<lim) {*t++=*s++;} |
6 | *t=0; |
7 | }
|
8 | return t; |
9 | }
|
also, wie Du's machst, machst Du's verkehrt, von daher alles Gut! Jobst Q. schrieb: > Oder man ersetzt es gleich konsistent durch t=stpcpylim(t,",",lim); Sehe ich auch so, ganz oder garnicht. Wenn Rechenleistung eine Rolle spielt, wird stpcpylim inline und gut iss. Und wenn der gebastelte Text nacher über eine Sio geht oder ans LCD, dann brauchen die Treiber schnell noch 1000 Takte pro Zeichen, da sind 3 Befehle Overhead Peanuts gegenüber dem Nutzen klarer Funktionen.
Frank M. schrieb: > Welchen Vorteil soll die gepackte Struktur gegenüber data[101] haben? Gar keinen, ich würde das persönlich auch niemals so mit einem Struct machen ... Aber der TE hatte nach so etwas gefragt :) Es wurden ja schon einige vielfach bewährte Lösungen gezeigt ... Sollte er eine davon nehmen :)
Hallo Zusammen, vielen Dank für die ganzen Tipps. Ich bin gerade etwas überfordert :-). Ich denke ich bleibe beim strlcpy, dass verstehe ich wenigstens. Die Gefahr, dass das mal irgendwann - nach irgendeinem Update - nicht mehr das tut was es jetzt tut, nehme ich in Kauf. Da ich mit den NodeMCU lediglich meine WordClock 12h Variante und kein KKW ansteuern möchte sehe ich da kein allzu großes Risiko :-). Aber ich möchte mich bei allen für die gezeigten Lösungen und die konstruktive Diskussion bedanken. Gruß Frank P.S. ich weiß schon, warum ich mit c nie wirklich warm werden konnte und das nach > 35 Jahren Softwareentwicklung.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.