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.
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.
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.
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
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.
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"
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
chartext[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.
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 ...
>chard[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.
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.
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.
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,constchar*s,constchar*lim){
2
3
lim--;
4
while(*s&&t<lim)*t++=*s++;
5
*t=0;
6
returnt;
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.
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".
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)
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
structmystruct{
2
chardata[100];
3
constcharterminator;
4
}__attribute__((packed));
Hmm, ich glaube direkt im Struct initialisieren geht nicht ... Die Frage
ist, was enthält dann "terminator"
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?
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:
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,constchar*s,constchar*lim)
2
{
3
if(t<lim--)
4
{
5
while(*s&&t<lim){*t++=*s++;}
6
*t=0;
7
}
8
returnt;
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.