Mal wieder ein Problem mit char und char*

Ich habe in meinem Sketch folgendes enthalten (nur auszugsweise zum besseren Verständnis):

//deklaration
.....
char strasse[30]="";
char strasseOhne[30]="";
......
void setup()
{
.......
}

void loop()
{
.....
finder.getString(">","<",strasse,30);
char* strasseOhne = leerErsetzen(strasse);   //Sprung ins Unterprogramm leerErsetzen mit Übergabe des Strings
Serial.println(strasseOhne);                               // veränderter String wird ordnungsgemäß im SM angezeigt
.......
dsplMsgSB();                                                          //Aufruf des UP zur Anzeige 
...........

}


// ******************** UP Entfernen/Ersetzen der Leerzeichen ******************** 
char* leerErsetzen(char zeichenKette[30])  // Leerzeichen im Straßennamen/Ort ersetzen
{
//Serial.print(zeichenKette);
int leseZeiger = 0;
int schreibZeiger = 0;
for(leseZeiger = 0; leseZeiger<31;leseZeiger++)
  { 
  if (zeichenKette[leseZeiger] != 0x20) 
    {  
    zeichenKetteModifiziert[schreibZeiger++]=zeichenKette[leseZeiger];
    }  
  else
    {                         
    zeichenKetteModifiziert[schreibZeiger++] = '%';
    zeichenKetteModifiziert[schreibZeiger++] = '2';
    zeichenKetteModifiziert[schreibZeiger++] = '0';
    } 
  } 
 schreibZeiger++;
 zeichenKetteModifiziert[schreibZeiger] = '\n';
 //Serial.print(zeichenKetteModifiziert);
 return(zeichenKetteModifiziert);
}

//********* Erstellen der Nachricht für Squeezebox-Server ************************

void dsplMsgSB()
{
  snprintf(SBox, 180, "GET /status?p0=display&p1=neuester%%20Benzinpreis:%%20&p2=%d,%d%d%%20%s,%%20%s&p3=30&player=* HTTP/1.0",u2,u3,v,brand,strasseOhne);  
...........
}

Soweit die wesentlichen Bestandteile. Der komplette Sketch liegt als Anhang bei. Er ist noch nicht geordnet, deshalb bittte ich Nachsicht zu üben.

Beim Kompilieren gibt es keine Fehlermeldung. Das Problem besteht darin, dass im UP dsplMsgSB() in dem Parameter 'strasseOhne' nichts mehr enthalten ist und somit die Strasse nicht angezeigt wird. Ich wundere mich, dass als Rückgabe aus dem UP leerErsetzen der Parameter zeichenKetteModifiziert nur als Zeiger (char*) ohne Fehlermeldung kompiliert wird.

Ändere ich die Rückgabe und char* strasseOhne in char, bekomme ich eine Fehlermeldung invalid conversion from 'char*' to 'char' in der letzten Zeile des UP leerErsetzen().

Offensichtlich wird im UP mit Zeigern gearbeitet, aber ich sehe nicht, wo ich einen Denkfehler mache. Kann mir jemand auf die Sprünge helfen? Nicht umsonst steht wohl in der Referenz "Pointers are one of the more complicated subjects for beginners in learning C". :slight_smile: :slight_smile:

Gruß Eberhard

V105_Benzinpreis_dspl_Squeezebox.ino (7.58 KB)

Soweit die wesentlichen Bestandteile.

Und was ist z.B. [b]zeichenKetteModifiziert[/b] oder
** **finder** **
?

Ich halte zwar nicht viel von "den ganzen Sketch bitte", weil das meist viel Arbeit macht, den ganzen Müll zu ignorieren,
aber die wesentlichen Sachen sollten schon vorkommen :wink:

Dann hast du zwei [b]char* strasseOhne[/b] die dir evtl. das Verständnis erschweren.

Ja, da fehlt wie die anderen Arrays deklariert sind.

Ich wundere mich, dass als Rückgabe aus dem UP leerErsetzen der Parameter zeichenKetteModifiziert nur als Zeiger (char*) ohne Fehlermeldung kompiliert wird.

Arrays sind lediglich Zeiger auf das erste Element. Array Variablen sind somit Zeiger. Solange das ein globales Arrays ist oder es als Parameter übergeben wurde funktioniert das. Man kann allerdings keine Zeiger auf lokale Variablen zurückgeben, da die nur temporär auf dem Stack existieren (was du aber nicht machst).

Das hier sieht hier sieht aber sehr, sehr falsch aus:

char* strasseOhne = leerErsetzen(strasse);

Du hast strasseOhne zwar mit 30 Bytes deklariert, aber weißt dann einfach den Rückgabe-Wert einem anderen Zeiger gleichen Namens zu (der nur lokal in loop() existiert und die globale Variable verdeckt). Egal welche Variable du da eigentlich meinst, beides ist falsch. Du kopierst da lediglich den Zeiger. Nicht den Inhalt. Den Zeiger zu kopieren ist völlig unnötig, und wenn du den Inhalt kopieren wolltest (was wahrscheinlich nicht der Fall ist), geht das so einfach nicht.

Aber du gibt ja einen Zeiger auf ein in deinem geposteten Code nicht deklariertes Array namens zeichenKetteModifiziert zurück. Was ist das?

Der normale Weg in C sowas zu machen ist das:

char* stringFunction(char* destination, char* source)
{
     ...

     return destination;
}

Man übergibt also die Quelle und das Ziel an die Funktion und gibt einen Zeiger an das Ziel-Array zurück. Aber beide Arrays sind außerhalb. Und dann weißt man den Rückgabe-Wert nichts anderem zu. Das geht zwar, ist aber unnötig, da man die Arrays ja schon mit ihren Namen ansprechen kann.

Der Rückgabe-Wert ist dafür da, dass man sowas machen kann:

Serial.println(stringFunction(...));

Die Funktion direkt in eine andere Funktion einsetzten, die einen char* als Parameter hat. Mehr nicht. Wenn du das nicht brauchst, kann man genauso void verwenden und nichts zurückgeben.

Ein paar andere Sachen:

char strasse[30]="";

Lass das = "" weg. Globale Variablen werden automatisch mit 0 initialisiert

char* leerErsetzen(char zeichenKette[30])

besser:

char* leerErsetzen(char* zeichenKette)
snprintf(SBox, 180, "GET....", ....);

Dein Format-String belegt zig Bytes RAM. Daher das hier machen:

snprintf_P(SBox, 180, PSTR("GET...."), ....);

Dann bleibt der String im Flash

Hallo,
"du den Inhalt kopieren wolltest (was wahrscheinlich nicht der Fall ist), geht das so einfach nicht"

char sZeit[9];
char sE1Zeit[9];

blah, blah

Ist das nicht einfach, oder ist es etwas anderes?
strcpy(sE1Zeit, sZeit);

Gruß und Dank
Andreas

Mit strcpy() geht das natürlich, aber nicht mit ptr1 = ptr2

Obwohl das hier glaube ich gar keine Kopie gewünscht ist. Wenn zeichenKetteModifiziert das eigentliche Ziel-Array ist, ist strasseOhne[30] überflüssig.

Man kann den Rückgabe-Wert schon einem Zeiger zuweisen wie in loop() gemacht. Der Teil ist an sich nicht falsch. Allerdings darf man diesen Zeiger dann nicht mit einem richtigen Array verwechseln (wie das hier schon global deklariert wurde). Dieser Zeiger ist lediglich ein Verweis auf das Ziel-Array.

Puh, das ist alles zusammen starker Toback für mich. Doch der Reihe nach:

@michael_x: Deshalb hatte ich den gesamt-Sketch als Anlage beigefügt.
@serenifly:

Ja, da fehlt wie die anderen Arrays deklariert sind.

deshalb hier:

char zeichenKetteModifiziert[30]="";           // Straßenname ohne Leerstellen (Leerstellen mit '%20' ersetzt)
char brand[30]="";                             // Name/Marke der Tankstelle
char strasse[30]="";                           // Straßenname der Tankstelle
char strasseOhne[30]="";                       // Straßenname ohne Leerzeichen
char ort[30]="";                               // PLZ und Ort der Tankstelle
char ortOhne[30]= "";
int zaehlerAnfragen=0;
char SBox[180];                                // Reservierter Speicherplatz für die Zeichenkette an den Server

Die anderen Tipps werde ich je nach Verständnis einarbeiten.

Allen, die sich hier beteilgt haben, jetzt schon mal meinen herzlichen Dank. Ich habe jetzt hier einige Hausaufgaben zu machen. Ich melde mich auf jeden Fall, wenn ich alles verdaut bzw. umgesetzt habe (oder wenn es wieder hakt)
.
... irgendwann wird das Thama auch ich meine Birne passen 8)

Eberhard

Hallo,

möchtest Du ein einzelnes Zeichen in einem char array ersetzen? Und das funktioniert nicht wie gedacht?

Den Anhang hatte ich nicht gesehen :confused:

Und wo soll der Unterschied zwischen zeichenketteModifiziert und strasseOhne sein? Das ist doch eigentlich das gleiche, was ich so sehe.

Die Funktion passt im Prinzip. Man könnte sie halt allgemeiner machen wenn du das Ziel-Array als Parameter übergibst statt mit einem globalen Array als Seiteneffekt zu arbeiten.
Es geht eher darum, was du danach machst. Dein geändertes Array ist danach zeichenketteModifiziert. Das kannst du nach dem Funktions-Aufruf direkt ausgeben.

Oder du machst das:

Serial.println(leerErsetzen(strasse);

Man auch sowas machen:

char* ptr = leerErsetzen(strasse);

Dein Haupt-Fehler ist wie gesagt wahrscheinlich, dass du den Rückgabe-Wert an eine Variable zuweist die lokal ein globales Array überdeckt:
http://de.wikibooks.org/wiki/C-Programmierung:_Funktionen#Verdeckung

Das macht daher nicht was du vielleicht denkst

Aber nochwas:
Deine Arrays sind beide 30 Zeichen groß. Wenn du im Quell-Array aber das Leerzeichen durch 3 andere Zeichen ersetzt muss das Ziel-Array größer sein. Da musst du sicher stellen dass das Ziel-Array groß genug ist.

Du iterierst im Quell-Array bis 30. Damit hast du sicher einen Puffer-Überlauf im Ziel-Array. Das ist dann doch schlimmer als die Sache mit dem Zeiger, da du damit in den Speicher der anderen Arrays schreibst. Sicherheitshalber solltest du abfragen ob der Index noch innerhalb des Ziel-Arrays ist.
Die Größe eines Arrays bekommst du mit sizeof():
http://en.cppreference.com/w/cpp/language/sizeof
Das ist wesentlich besser die Größe immer per Hand anzugeben.

Außerdem muss am Ende auf jeden Fall ein Null-Terminator stehen! Besser du schreibst am Ende ein '\0' (oder 0), statt dem Linefeed. Auch für den muss Platz im Array sein. Ein C String der Größe 30 hat 29 sichtbare Zeichen und ein Null am Ende:

Und wo soll der Unterschied zwischen zeichenketteModifiziert und strasseOhne sein? Das ist doch eigentlich das gleiche, was ich so sehe.

Von der Sache gebe ich Dir Recht, dass in Beiden das Gleiche steht. strasseOhne in der Loop und zeichenKetteModifiziert im UP. Ich will das UP nochmal aufrufen um das Leerzeichen zwischen PLZ und Ort umzu wandeln.

Alle Serial.print()-Befehle sind nur zur Fehlersuche.

Ich rufe das UP aus der Loop mit leerErsetzen(strasseOhne,strasse); auf. Das UP startet mit

char* leerErsetzen(char* zeichenKetteModifiziert,char* zeichenKette) und gibt mit return(zeichenKetteModifiziert); den Strassennamen mit ersetzten Leerzeichen zurück. Das funktioniert so wie gewollt. Danke für die Hilfestellung.

Ein erneuter Aufruf der Unterroutine mit leerErsetzen(ortOhne,ort); wird nicht mehr richtig aufgerufen (warum ist mir noch unklar)..

Edit: Lag in der Tat in der Größe der modifizierten Ausgabe-Arrays.

Ich muss das alles ersteinmal 5-mal lesen bevor ich das langsam kapiere.

Edit: das Thema mit der Größe ist mir bewusst. Führte zwar nicht zu Fehlern, sollte bei einer sauberen Programmierung berücksichtigt werden - und wird es auch werden.

Gruß
Eberhard

Den Fehler hast du vielleicht noch nicht bemerkt weil du dadurch in den Speicher von ort[30] reinschreibst. Oder allgemein in die Variable(n) die nach deinem Ziel-Array stehen. C bringt dir hier anders als manche andere Sprachen keine Fehler wie "array index out of bounds".

Was bei sowas wie gesagt auch Fehler bei der Ausgabe und Weitergabe an andere Funktionen verursacht ist eine inkorrekte oder fehlende Null-Terminierung.
Wobei das glaube ich sogar passen sollte, da du über das gesamte Array iterierst und dann den Terminator am Ende mit kopierst. Dann bringt es allerdings nichts noch ein LF danach einzufügen:

zeichenKetteModifiziert[schreibZeiger] = '\n';

Alle Funktionen brechen ab wenn der Terminator kommt. Was dahinter steht wird ignoriert.

Wegen sizeof() nochmal. Du kannst damit z.B. sizeof(strasse) machen. Was aber nicht geht ist das:

char* leerErsetzen(char* zeichenKetteModifiziert,char* zeichenKette)  
{
      int size = sizeof(zeichenKette);
}

Das gibt dir immer 2 zurück. Die Größe eines Zeigers auf einem AVR Prozessor. Wenn du die Größe des Arrays variabel in der Funktion haben willst (statt fest mit 30), musst du sie als 3. Parameter übergeben.

Da du hier mit Strings hantierst, ist das aber nicht unbedingt nötig. Zumindest nicht bei der Quelle. Man iteriert dabei i.d.R. solange über das Array bis der Null-Terminator kommt:

for(leseZeiger = 0; zeichenKette[leseZeiger] != NULL; leseZeiger++)

Oder mit einer while-Schleife. Dann ist es egal wie lange das Array ist man muss die Größe nicht extra verwalten.

Wobei du mit der Variante wieder aufpassen musst, da hier der Terminator nicht kopiert wird. Den kann aber dann per Hand am Ende einfügen. Sowie du jetzt das LF am Ende schreibst.

Die Größe des Ziel-Arrays kann man dagegen vielleicht als Parameter übergeben. Das wird auch bei Standard Funktionen wie strncpy() so gemacht. Und dann nur solange schreiben wie noch Platz im Ziel-Array ist.

Wieso die Funktion nicht wiederverwendbar ist, sehe ich so aber auch nicht wirklich.

Hallo,

ich mußte mich damit auch befassen um einen Dezimalpunkt mit einem Komma zu ersetzen. Dabei habe ich eine Funktion gefunden die wunderbar funktioniert und übersichtlich zu verstehen ist und kein 2. char array als Zwischenspeicher benötigt. Eine Zeichenkettenlänge muß auch nicht beachtet werden, weil immer bis zum Ende, bis zum Null-Terminator, geguckt wird.
Vielleicht ist das besser geeignet und hilft Dir weiter.

void Zeichen_in_charArray_ersetzen (char* zeichenkette, char charSearch, char charReplace)
{
  int i = 0;
  while ( zeichenkette[i] != '\0')  {
    if ( zeichenkette[i] == charSearch )  {   // suche nach Zeichen 'charSearch' und
      zeichenkette[i] = charReplace;          // ersetze es durch 'charReplace'
    }
  i++;
  }
  
}

aufgerufen in loop sieht bei mir wie folgtaus

hier im übergebenen char array "AD_Channel_3" wird wie gesagt der Dezimalpunkt durch ein Komma ersetzt.

Zeichen_in_charArray_ersetzen(AD_Channel_3, '.', ',');

Alternativ wenn man nur ein Zeichen ersetzen muss (wie bei "111.222"):

char* ptr = strchr(zeichenkette, '.');
if(ptr != NULL)
   *ptr = ',';

Man kann auch strchr() wiederholt mit ptr als Argument aufrufen wenn man mehr ersetzen muss.

Das geht aber bei ihm nicht, da er ein Zeichen durch 3 ersetzen muss. Dadurch kann man das nicht in situ machen und muss den String in einem anderen Array neu aufbauen.

Was man eventuell machen könnte ist jedes Zeichen einzeln zu verschicken statt erst in einen Puffer zu schreiben und dann zu schicken. Das erzeugt aber wahrscheinlich mehr Overhead bei der Ethernet Übertragung.

@Doc: Einen einzelnen Buchstaben zu ersetzen ist einigermassen einfach.
Das Problem hier ist, ein einzelnes Zeichen " " durch eine längere Sequenz "%20" zu ersetzen.

Das ginge zwar auch innerhalb eines einzigen char arrays, wenn es genügend Platz für die verlängerte Ziel-Version hat, aber wenn der RAM ganz so knapp nun doch nicht ist, vereinfacht ein separater Ergebnis-Puffer das Ganze deutlich.

Der Schreibzeiger oder Zähler für das Ziel-Feld muss natürlich im Auge behalten werden, und die EndeKennung - 0 muss auch in das Ziel-Feld passen.

@Serenifly: Man könnte den Sendetext in Abschnitte zwischen den ersetzten Leerzeichen zerschnipseln,
oder sich fragen, wo der SendeText eigentlich her kommt, und ihn gleich beim Einlesen (SD Card, Flash, ? ) aufbereiten...

Das was Doc_Arduino da macht :

Code:

void Zeichen_in_charArray_ersetzen (char* zeichenkette, char charSearch, char charReplace)
{
  int i = 0;
  while ( zeichenkette[i] != '\0')  {
    if ( zeichenkette[i] == charSearch )  {   // suche nach Zeichen 'charSearch' und
      zeichenkette[i] = charReplace;          // ersetze es durch 'charReplace'
    }
  i++;
  }
  
}

Also zeichenkette innerhalb der funktion wieder als Array anzusprechen also so zeichenkette[index] funktioniert klar.

Es müsste aber so gehen, ist umständlicher wäre aber logisch.

Beim Aufruf von Zeichen_in_charArray_ersetzen zeigt der Char Pointer char* zeichenkette auf das erste Zeichen des Charstrings das irgendwo im Speicher liegt.

Wenn ich nun z.b auf das zweite Zeichen zugreifen möchte und ein zeichenkette++ mache würde der Zeiger auf das nächste Char zeigen, und hätte dann mit *zeichenkette Zugriff auf dessen Inhalt.

Ist mein Denken richtig ?

Die Diskussion beeindruckt mich zutiefst und zeigt mir, dass das Thema auch bei den Freaks nicht trivial ist. Um unnötige Diskussionen zu vermeinden, möchte ich kurz zur Verdeutlichung meine Aufgabenstellung euch aufzeigen.

Ich lese aus dem Internet in bestimmten Zeitabständen den aktuellsten Treibstoffpreis aus. Dieser wird aufbereitet und dann zu einen LogitechMediaServer (LMS) auf einem Rasp gesendet, der diese Nachricht auf allen angeschlossenen Mediaplayern anzeigt. Leider hat der LMS seine Probleme mit jeglicher Art von Sonderzeichen (Leerzeichen, Umlaute und "ß"), die in LMS-verträglichen Code umgewandelt werden müssen. Ein Leerzeichen muss als %20 dargestellt werden, entsprechend wollen die anderen Sonderzeichen behandelt werden (ß => ss, usw.). Es geht nicht darum ein Zeichen gegen ein anders auszutauschen, sondern z.B. ein "ö" gegen ein "oe" zu verändern.

Hier ein kleiner Auszug(keine Probleme):

switch(zeichenKette[leseZeiger]) 
   {
    case 0x20:                  // Ersetze leerzeichen durch %20
     zeichenKetteModifiziert[schreibZeiger++] = '%';
     zeichenKetteModifiziert[schreibZeiger++] = '2';
     zeichenKetteModifiziert[schreibZeiger++] = '0'; 
     break; 
  
    case 0xB6:                   // Ersetze ö durch oe 
    zeichenKetteModifiziert[schreibZeiger++] = 'o';
    zeichenKetteModifiziert[schreibZeiger++] = 'e';
    break;
........

Ich habe mich hierzu inzwischen für switch/case entschieden, da mehrere verschiedene Varianten auftreten können. Da bin ich zum Glück inzwischen gut unterwegs.

Mein Thema ist nur noch das von Serenifly (zu Recht) beanstandete Speichermanagement. Da bin ich aber bereits dabei. Ich werde, wenn alles bisher Erstellte zu meiner Zufriedenheit läuft, noch mal präsentieren und zur Diskussion stellen. Das kann aber noch eine Woche dauern, weil ich a) das Thema durcharbeiten m öchte (abschreiben kann jeder) und b) noch andere Aufgaben zu erledigen habe, die auch einen zeitlichen Anschlag haben.

Ich finde die Diskussion toll. Dieses Forum mit dem hier gezeigten Respekt den Anfängern gegenüber, soll mal ein anderes Forum nachmachen. DANKE EUCH!!

Viele Grüße
Eberhard

Kannst du das dann nicht schon beim Lesen aus dem Internet anders abspeichern? Also statt erst mal das Original komplett abzuspeichern und dann eine modifizierte Kopie erstellen, die Zeichen aus dem Internet Stück für Stück lesen und vor dem Speichern entsprechend verändern.

Beim Aufruf von Zeichen_in_charArray_ersetzen zeigt der Char Pointer char* zeichenkette auf das erste Zeichen des Charstrings das irgendwo im Speicher liegt.

Wenn ich nun z.b auf das zweite Zeichen zugreifen möchte und ein zeichenkette++ mache würde der Zeiger auf das nächste Char zeigen, und hätte dann mit *zeichenkette Zugriff auf dessen Inhalt.

Ist mein Denken richtig ?

Hallo,

fast richtig.
zeichenkette++ würde zu zeichenkette eins addieren. Geht natürlich nicht.
Wie in der Funktion, muß man den Index des char arrays um eins erhöhen um auf das nächste Zeichen in der zeichenkette zugreifen zu können. Genau wie es in der Funktion gemacht wird.

Das char array namens Zeichenkette ist quasi nur eine Aneinanderreihung von einzelnen Zeichen. Mit der Indexnummer kann man dann auf jedes einzelne zugreifen. Zur Verdeutlichung, Serenifly hatte das schon einmal so geschrieben zum besseren Verständnis.
In meinem Fall wäre die original erzeugte char array "zeichenkette" zum Bsp.: 13.92
ausführlich geschrieben wäre das
char array zeichenkette[] {1, 3, '.', 9, 2}
Das letzte unsichtbare Zeichen \0 lasse ich mal weg.
Mit zeichenkette[2] (Indexnr. 2) hätte man den Dezimalpunkt in der Mangel ...

Doc_Arduino:
zeichenkette++ würde zu zeichenkette eins addieren. Geht natürlich nicht.

Das geht sehr wohl und wird ständig gemacht. Auch hier wieder: wir arbeiten hier mit Zeigern

Zeiger kann man beliebig addieren, subtrahieren und vergleichen:

http://www.tu-chemnitz.de/urz/kurse/unterlagen/C/kap2/ptrarith.htm

Der Subscript Operator ist lediglich syntaktischer Zucker für Zeiger-Arithmetik. Das hier ist genau das gleiche:

int test[10];
test[5] = 2;
int test[10];
*(test + 5) = 2;

Aufpassen muss man da bei dem Vorrang der Operatoren. Das hier dereferenziert einen Zeiger und inkrementiert ihn danach:

*ptr++

Wenn man den Inhalt der Adresse inkrementieren will braucht man Klammern:

(*ptr)++;

@serenifly:

Kannst du das dann nicht schon beim Lesen aus dem Internet anders abspeichern? Also statt erst mal das Original komplett abzuspeichern und dann eine modifizierte Kopie erstellen, die Zeichen aus dem Internet Stück für Stück lesen und vor dem Speichern entsprechend verändern.

Da die Internetseite sehr groß ist und ich beim Eingang bestimmte Zeichenkette suchen, würden bei der gleichzeitigen Zeichensubstuierung am Eingang Fehler auftreten. Diesen Fehler hatte ich schon am Anfang. Deshalb bevorzuge ich die step-by-step Varaiante. Die Internetseite passt auch nicht mehr in den Uno.

Ich war so frei und habe meine for-next Schleife gegen Deine Lösung mit while (zeichenKette[leseZeiger] != '\0') getauscht. Klappt wunderbar.

Das Thema snprintf(... ) im Flash-Speicher zu belassen, ist als nächstes dran.

Eberhard

Hallo,

@ Serenifly. Jetzt wird es verkompliziert, denke ich. rudirabbit wollte nicht mit Zeichenketten rechnen, er wollte das nächste Zeichen in der Zeichenkette mit zeichenkette++ haben. Das geht natürlich nur über die Indexnummer des char arrays.