Nachdem mir hier - Beitrag "Grösse eines Array of pointers"
schon so toll geholfen wurde, hab ich nochmal ne Frage an die
Spezialisten.
Zur Ausgabe einer string constanten habe ich 2 Varianten probiert. Beide
funktionieren erwartungsgemäß.
(Der printf ist nur zum Test. Im Original steht da eine Funktion, die
zeichenweise ausgeben muss.)
1.
1
voidsendString(char*str){
2
do{
3
printf("%c",*str);
4
}while(*str++=*(str+1));
5
}
2.
1
voidsendString(char*str){
2
inti=0;
3
while(*(str+i)){
4
printf("%c",*(str+i));
5
i++;
6
}
7
}
Variante 1 spuckt aber eine Reihe Compilerwarnungen aus, die ich nicht
wegbekomme.
Reiner W. schrieb:> Ja, ihr könnt sagen, steht doch alles da. Aber ich kapier's trotzdem> nicht und bekomme die Warnung nicht weg.
In der Tat steht alles da. ;-)
> (Der printf ist nur zum Test. Im Original steht da eine Funktion, die> zeichenweise ausgeben muss.)
(Es gibt auch putchar() ;-)
> } while (*str++ = *(str+1));> test.cpp:23:22: warning: using the result of an assignment as a> condition without parentheses
Nun, in der Flüchtigkeit vergisst manch einer schnell mal, bei einem
Vergleich das doppelte Gleichheitszeichen zu schreiben:
1
if(i=42){printf("hurra!\n");}// soll i == 42 sein
Das ist zwar syntaktisch korrekt (es wird eine Zuweisung ausgeführt,
deren Wert dann die Bedingung darstellt), aber halt semantisch in aller
Regel nicht das, was der Programmierer wollte.
Daher muss man eine Zuweisung innerhalb einer Steuerstruktur explizit
machen, um die Warnung zu vermeiden. Entweder:
1
}while((*str++=*(str+1))!=0);
oder
1
}while((*str++=*(str+1)));
Die doppelt notierten Klammern haben sich eingebürgert für "ja, das soll
wirklich eine Zuweisung sein, deren Wert hier benutzt wird".
> test.cpp:23:19: warning: unsequenced modification and access to 'str'> [-Wunsequenced]> } while (*str++ = * (str+1));> ^ ~~~
Die Reihenfolge, in der jeweils das Dererenzieren des Zeigers und das
Inkrementieren (++) erfolgen, ist durch den Standard nicht
festgeschrieben. Daher ist das Resultat der Aktion so genanntes
"undefined behaviour". Sowas darf man einfach nicht in eine Zeile
schreiben, sondern es muss sich zwischen beiden Aktionen ein so
genannter "sequence point" befinden. Es gibt eine Reihe von definierten
sequence points in C; der einfachst davon ist das abschließende
Semikolon hinter einer Anweisung.
Herzlichen Dank für die super Erklärung! Und deine Mühe!
Jörg W. schrieb:> (Es gibt auch putchar() ;-)
Ich sag ja war nur ein Testschnipsel, ich kämpfe halt immer noch mit dem
Pointerverständnis und probiere deshalb alles Mögliche aus.
Aber es wird schon besser ;-)
Das mit den doppelten Klammern habe ich inzwischen schon rausgefunden.
Aber du hast die perfekte Erklärung nachgeliefert.
Nichts frustriert mich mehr, als wenn etwas klappt und ich nicht weis
warum ;-)
Reiner W. schrieb:> Nachdem mir hier - Beitrag "Grösse eines Array of pointers"> schon so toll geholfen wurde, hab ich nochmal ne Frage an die> Spezialisten.>> 1.void sendString(char* str) {> do {> printf("%c",*str);> } while (*str++ = *(str+1));> }>
Warum willst Du das überhaupt so machen? Kopiert man dann mit "while
(*str++ = *(str+1))" nicht den Wert des nächsten Zeichens auf das
derzeitige Zeichen?
Stringpointer schrieb:> Warum willst Du das überhaupt so machen?
Sry, da muss ich mich entschuldigen. Ich mach das nicht so, die Fragen
dienten eher dazu etwas von meinem Pointernebel im Hirn zu lichten;-)
Nach dem Motto, dass müßte eigentlich auch so gehen...
Tatsächlich mach ich das so:
1
voidsendString(char*str){
2
while(*str){
3
// do something with the char
4
printf("%c",*str);
5
6
str++;// next char in string
7
}
8
}
Wobei statt des printf eine func angesprungen wird, die zeichenweise auf
nem STM eine PS2-Tastatur emuliert.
Stringpointer schrieb:> Kopiert man dann mit "while> (*str++ = *(str+1))" nicht den Wert des nächsten Zeichens auf das> derzeitige Zeichen?
Jepp, aber das passiert ja innerhalb der func und der orinale const
string merkt da nichts von.
Deswegen wird immer noch der selbe string ausgegeben, wenn ich die
function mehrfach aufrufe.
Aber wie gesagt, ich bin nicht so oft in c unterwegs und deshalb will
ich mich da lieber mit meinem (vlt. gefährlichen) Halbwissen
zurückhalten.
Reiner W. schrieb:> void sendString(char* str) {> while (*str) {> // do something with the char> printf("%c", *str);>> str++; // next char in string> }> }
Der übliche Weg, die doppelte Dereferenzierung des Zeiges zu vermeiden,
wäre eine Hilfsvariable:
1
charc;
2
while((c=*str)!='\0'){
3
// do something with the char
4
printf("%c",c);
5
6
str++;// next char in string
7
}
Derartige Beispiele finden sich schon im guten alten Kernighan/Ritchie.
;-)
Dort wird auch gezeigt, dass folgende Verkürzung OK ist:
1
charc;
2
while((c=*str++)!='\0'){
3
// do something with the char
4
printf("%c",c);
5
}
Beachte, dass anders als in deinem Code hier der Zeiger nur einmal
dereferenziert wird, und das zugegriffene Objekt (vom Typ "char") wird
auch nur gelesen (was immer eine gute Idee ist in so einem Falle).
Dass man das im K&R als "Mikro-Optimierung" so benutzt hat, hängt u.a.
damit zusammen, dass die PDP-11 (auf der C entwickelt worden ist), für
*p++ und *--p nativ eigene Adressierungsmodi besaß (die letztlich dort
auch für den Stack-Zugriff benutzt worden sind). Das dürfte primäre
Motivation gewesen sein, solche Ausdrücke auch in C zuzulassen, denn
damit ließen sich mit der eher bescheidenen Optimierung damaliger
Compiler trotzdem noch die Möglichkeiten der Maschine ausnutzen.
Heutzutage ist das den Compilern natürlich herzlich egal, ob du das
Dereferenzieren des Zeigers und das Inkrementieren so "schön C-mäßig" in
einen Ausdruck setzt oder im Code aufteilst.
Jörg W. schrieb:> Derartige Beispiele finden sich schon im guten alten Kernighan/Ritchie.> ;-)
Ja, immer drauf ;-) Ist alles schon solange her...
Also wenn ich deine Ausführungen richtig verstehe, ist der code nicht
old scool, gibt aber keinen zwingenden Grund eine extra Variable zu
investieren? Jedenfalls meckert mein compiler auch nicht.
Jörg W. schrieb:> und das zugegriffene Objekt (vom Typ "char") wird> auch nur gelesen (was immer eine gute Idee ist in so einem Falle).
Ja, das Argument kapier ich. Danke.
Reiner W. schrieb:> Also wenn ich deine Ausführungen richtig verstehe, ist der code nicht> old scool, gibt aber keinen zwingenden Grund eine extra Variable zu> investieren?
Die Konstellation, dass man ein Zeichen bis zur 0 auslesen und
verwenden will, und anschließend den Ptr erhöhen, ist in C immer
unbefriedigend.
Entweder hat man eine Zuweisung in einer Bedingung, z.B.:
1
while((c=*ptr++)!=0){
oder man hat die Zuweisung doppelt, z.B.:
1
for(c=*ptr++;c;c=*ptr++){
oder man hat die Abfrage doppelt
1
do{
2
if(*ptr)..doSomething(*ptr);
3
}while(*ptr++);
Dein Erster Code ist doppelt falsch:
a) Er manipuliert die Quelle und
b) testet das erste Zeichen nicht.
Ohne a) wäre es einfach:
1
voidsendString(char*str){
2
do{
3
printf("%c",*str);
4
}while(*++str);
5
}
Das geht lange gut, bis irgendwann mal ein String von 0 Zeichen Länge
kommt.
Einfach wird es, wenn die 0 auch mit ausgegeben wird:
1
voidsendString(char*str){
2
do{
3
printf("%c",*str);
4
}while(*str++);
5
}
Darum ist z.B. strcpy in der einfachsten Version sehr klein.
A. S. schrieb:> Das geht lange gut, bis irgendwann mal ein String von 0 Zeichen Länge> kommt.
Ja, danke für die ausführlichen Ausführungen. Habs schon geändert.
Jörg W. schrieb:> Dort wird auch gezeigt, dass folgende Verkürzung OK ist:> char c;> while ((c = *str++) != '\0') {> // do something with the char> printf("%c", c);> }
Prinzipiell ja, allerdings verliere ich so das erste Zeichen, deshalb
die do..while
Reiner W. schrieb:> Prinzipiell ja, allerdings verliere ich so das erste Zeichen, deshalb> die do..while
äh, nein. Alle Versionen sind äquivalent zu Deiner zweiten. Du musst in
der Schleife dann natürlich c nutzen und nicht *ptr.
Oder was möchtest Du genau? Wenn ein String "AB" mit abschließender 0
kommt:
1) A, B
2) A, B, 0
Das grundlegende Problem, was Du hier hast, und was die folgenden Fehler
möglich macht, ist, dass Du hier in der Parameterliste einen sog.
Output-Parameter (bzw. Input/Output) deklariert hast. Output-Parameter
sollten aber die große Ausnahme sein. Du möchtest ja den C-String des
Aufrufers nicht ändern.
Daher besser so:
1
voidsendString(constchar*str){// das Zielobjekt ist read-only
2
assert(str);
3
do{
4
printf("%c",*str);
5
}while(*str++=*(str+1));
6
}
Das deckt die ungewollte Zuweisung sofort klar auf.
Zudem: der nullptr der Wert NULL 0 gehört bei Deinem Code nicht zur
zugelassenen Wertemenge des Zeiger-Parameters str. Wenn Du bei rohen
Zeigern bleiben willst, dann solltest Du eine Vorbedingung in Form einer
Zusicherung einbauen, um den Wertebereich von str einzuschränken.
Beide Maßnahmen helfen gerade Programmieranfängern dabei, den Code klar
zu formulieren bzw. mehr über seine Wirkung zielgerichtet nachzudenken.
Abgesehen davon, sollten Parameterdefinitionen (und lokale Variablen,
wenn möglich) immer read-only sein, damit ein schleichender
Bedeutungswechsel der Variablen ausgeschlossen ist. Und dort, wo das
nicht geht, sollte der Gültigkeitsbereich möglichst eingeschränkt
werden.
1
voidsendString(constchar*conststr){
2
assert(str);
3
for(constchar*iter=str;*iter!='\0';iter+=1){
4
printf("%c",*iter);
5
}
6
}
Hier ist str in der gesamten Funktion der C-String, den der Aufrufer
übergeben hat, und nicht nur ein Teil davon.
Der Iterator iter ist auf die for-Schleife im Gültigkeitsbereich
begrenzt und hat einen Namen, der seinen Zweck besser beschreibt.
Mache Dir keine Gedanken um Laufzeit, vertraue dem Compiler!
Optimierungen sind die Aufgabe des Compilers, die er sehr gut kann, wenn
Du präzise in Deiner programmatischen Aussage bist.
Wilhelm M. schrieb:> dass Du hier in der Parameterliste einen sog.> Output-Parameter (bzw. Input/Output) deklariert hast.Wilhelm M. schrieb:> Zudem: der nullptr der Wert NULL 0 gehört bei Deinem Code nicht zur> zugelassenen Wertemenge des Zeiger-Parameters str.
Oh, man. Wußte gar nicht, dass ich in so ein kleines Code-Schnipsel so
viele Fußangeln packen kann.
Wilhelm M. schrieb:> Hier ist str in der gesamten Funktion der C-String, den der Aufrufer> übergeben hat, und nicht nur ein Teil davon.> Der Iterator iter ist auf die for-Schleife im Gültigkeitsbereich> begrenzt und hat einen Namen, der seinen Zweck besser beschreibt.
Hab's kapiert, nun muss ich es nur noch verinnerlichen.
@all ursprüngliche Frage perfekt beantwortet, dazu noch viele sehr
sinnvolle Tips bekommen, wie man es besser machen kann! Was will man
mehr.
Zumindest in diesem Forum werde ich das wohl nie zurückgeben können.
Danke
Ja, sehe ich genau so. Den String solltest Du nicht verändern und als
"const char* str" belassen. Stringliterale habe immer diesen Typ. Da
zeigt sich meist auch ein typischer Anfängerfehler, zum Beispiel bei der
Übergabe an eine Funktion "void send(char* str)" konstante Strings nach
"(char*)" zu casten "weil der Compiler sonst meckert", ohne zu wissen,
warum er damit Probleme hat. Er wirft dort manchmal Warnungen, wenn man
konstante Strings einer Funktion übergibt, die veränderliche Strings
erwartet.
Wenn Du also eine Funktion schreibst, die den String nur ausgeben soll,
dann sieht meine Lieblingsversion wie folgt aus:
1
voidsend(constchar*str){
2
while(*str){
3
printf("%c",*str++);
4
}
5
}
Das ist die einfachste Variante. Den Nullpointercheck, wie ihn Wilhelm
M. per assert vorgeschlagen hat, kann man natürlich auch noch einfügen.
Oder für Minimalcode-Fetischisten die while-Prüfung auf "while(str &&
*str)" erweitern und hoffen, dass der Compiler das ordentlich optimiert
:D
Die hier dargestellte Variante würde ich sogar noch bevorzugen, da bei
der Übergabe des Strings vor (!) der Ausgabe gecheckt wird, ob das
Stringende a la \0 bereits erreicht ist. Die do-while-Schleifen geben
immer ohne check das erste Zeichen aus (zum Beispiel übergabe von "").
Das kann je nach Schnittstelle hinderlich oder nur unschön sein.
Bei der Veränderlichkeit des Pointers selbst sehe ich prinzipiell keine
Probleme, wenn Du den originalen Wert des Pointers nur einmal brauchst.
Das spart Dir dann die Hilfsvariable. Für den Code außerhalb der
Funktion wird ja an dem übergebenen Variablen, also der Adresse des
Strings, nichts verändert.
Reiner W. schrieb:> gibt aber keinen zwingenden Grund eine extra Variable zu investieren?
Du "investierst" da nichts. Die Variable wird am Ende einfach nur ein
CPU-Register sein.
Lös dich mal von dem Gedanken, dass das, was du da hinschreibst, auch
nur annähernd 1:1 durch den Compiler auf die CPU umgesetzt wird. Das mag
vor 45 Jahren auf einer PDP-11 so gewesen sein.
Stringpointer schrieb:> Den String solltest Du nicht verändern und als> "const char* str" belassen. Stringliterale habe immer diesen Typ.
Das ist leider falsch: in C ist der Typ char[N] und in C++ ist er const
char[N]. Daher zerfällt (decay) der Stringbezeichner dann in C zu char*
und in C++ zu const char*.
Wenn man in C dann versucht ein Zeichen zu modifizieren, stellt man zur
Laufzeit fest, das der Compiler das Literal in einer ro-section plaziert
hatte.
Stringpointer schrieb:> Das kann je nach Schnittstelle hinderlich oder nur unschön sein.
Nein, es ist einfach falsch. Denn das `\0` gehört zu dem C-String nicht
dazu. Es ist nur das Sentinel.
Reiner W. schrieb:> Oh, man. Wußte gar nicht, dass ich in so ein kleines Code-Schnipsel so> viele Fußangeln packen kann.
Ja, das Problem an C ist, dass die Sprache in vielen Dingen "zu einfach"
ist, und gerade dem Anfänger daher zu viel Verantwortung überlasst.
Stringpointer schrieb:> dann sieht meine Lieblingsversion wie folgt aus:> void send(const char* str) {> while(*str) {> printf("%c", *str++);> }> }>> Das ist die einfachste Variante.
Ich halte hier die Manipulation des Schleifenwertes in einer komplexen
Anweisung irgendwo in der Schleife für unglücklich.
Also entweder explizit am Ende oder gleich als for(; *str; str++)