Forum: Mikrocontroller und Digitale Elektronik strcpy mit automatischer Längenprüfung


von Frank L. (Firma: Flk Consulting UG) (flk)


Lesenswert?

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.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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
von MaWin (Gast)


Lesenswert?

Frank L. schrieb:
> Gibt es eine c Standardfunktion die beides erledigt,

Nein.

strlcpy gibt es unter Windows und BSD.

Schreib sie dir selbst.

von Frank L. (Firma: Flk Consulting UG) (flk)


Lesenswert?

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

von Volker B. (Firma: L-E-A) (vobs)


Lesenswert?

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

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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.

von Mixer (Gast)


Lesenswert?

Ohne Aufwand ganz simpel:

char d[100], *s;

strncpy(d, s, sizeof(d))[sizeof(d) - 1] = '\0';

von Irgendwer (Gast)


Lesenswert?

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

von Volker B. (Firma: L-E-A) (vobs)


Lesenswert?

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

von Defender (Gast)


Lesenswert?

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... ;-)
*/

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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
von MaWin (Gast)


Lesenswert?

Defender schrieb:
> FERTIG

Compiler error so bald Quelle nicht-modifizierbar, also const, ust, 
zudem nicht multithreadtauglich.

von A. S. (Gast)


Lesenswert?

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.

von A. S. (Gast)


Lesenswert?

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 Mampf F. (mampf) Benutzerseite


Lesenswert?

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 Andreas E. (hismastersvoice)


Lesenswert?

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.

von DanVet (Gast)


Lesenswert?

Da es noch keiner genannt hat
snprintf()

von A. S. (Gast)


Lesenswert?

DanVet schrieb:
> Da es noch keiner genannt hat
> snprintf()

oder direkt sprintf(array, ".30s", quelle);

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Achim S. schrieb:
> oder direkt sprintf(array, ".30s", quelle);

Danach steht in array der String ".30s" und nicht der Inhalt von quelle.

von Marco H. (damarco)


Lesenswert?

Nun warum unbedingt die Standard Bibliothek bemühen? Die 30 Zeichnen 
kann man viel schlanker in einer einfachen FOR Schleife kopieren.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

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 ...

von Fragomat (Gast)


Lesenswert?

>
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)

von Mampf F. (mampf) Benutzerseite


Lesenswert?

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
von A. S. (Gast)


Lesenswert?

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:

von Jack (Gast)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

Seit C11 (also schon recht lange) gibt es strncpy_s(), was auch 
aufwandstechnisch wegen fehlendem padding vorzuziehen ist.

: Bearbeitet durch User
von Marco H. (damarco)


Lesenswert?

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
von Jobst Q. (joquis)


Lesenswert?

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
von Fragender (Gast)


Lesenswert?

Jobst Q. schrieb:
> lim ist ein Zeiger auf das erste Byte, dass nicht beschrieben werden
> darf

Und warum beschreibst Du es u.U. trotzdem?

von A. S. (Gast)


Lesenswert?

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.

von A. S. (Gast)


Lesenswert?

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.
von Fragomat (Gast)


Lesenswert?

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));

von Mampf F. (mampf) Benutzerseite


Lesenswert?

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
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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
von Jobst Q. (joquis)


Lesenswert?

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
von A. S. (Gast)


Lesenswert?

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.

von Mampf F. (mampf) Benutzerseite


Lesenswert?

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 :)

von Frank L. (Firma: Flk Consulting UG) (flk)


Lesenswert?

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
Noch kein Account? Hier anmelden.