Strcpy - Pointer in funktion übergeben und zurückgeben - Verständnisfrage

Hallo,

ich fange gerade an mich mit Pointern zu beschäftigen und habe hier gerade ein Verständnis Problem und hoffe, ihr könnt mir eine Antwort geben. Ich möchte ein char Array als Pointer an eine Funktion übergeben, den Inhalt verändern und nach Aufruf der Funktion erwarte ich den geänderten Inhalt an der Stelle des Pointers zurück.

Unterprogramm:

void test (char *abc) {
	char *abcde = "Test1234";
	Serial.println (abcde);
	strcpy(abc, abcde);      // Inhalt von abcde nach abc kopieren
	Serial.println (abc);    // Ausgabe: "Test1234"
}

Aufruf:

   char *xyz;  // oder char xyz[256];
   xyz = (char*)"Hallo";
   test (xyz);
   Serial.println(xyz);      // Ausgabe "Test1234" - funktioniert!

Wenn ich nun im Unterprogramm folgendes schreibe, bekomme ich nach dem Aufruf nur das "Hallo" zurück:

void test (char *abc) {
    abc = (char*)"Test1234";  // Array Inhalt hier ändern
    Serial.println (abc);     // Ausgabe hier noch: "Test1234"
}

Aufruf:

   char *xyz;  // oder char xyz[256];

   xyz = (char*)"Hallo";
   test (xyz);
   Serial.println(xyz);      // Ausgabe "Hallo" - "Test1234" erwartet

Frage:
was macht "strcpy(abc, abcde);" mit dem Pointer, das durch diesen Aufruf das Array "Test1234" wieder in xyz im Hapuptprogamm auftaucht? Wird strcpy nicht aufgerufen, wird es zwar im Unterprogramm lokal geändert, nach Rücksprung zum Hauptprogramm ist in xyz wieder der alte Inhalt vorhanden.

Viele Grüße und ein glückliches und zufriedenes Jahr 2025,

Horst

Das Problem liegt darin, dass im zweiten Beispiel, wenn Sie abc = (char*)"Test1234"; zuweisen, Sie nur den Zeiger abc lokal in der Funktion test() ändern, was den Wert des Zeigers xyz in der Hauptfunktion nicht verändert. Im Gegensatz dazu kopieren Sie mit strcpy(abc, abcde); den Inhalt der Zeichenkette abcde in den Speicherbereich, auf den abc zeigt, was den Inhalt des Speichers, auf den xyz zeigt, ändert und diese Änderung nach der Rückkehr aus der Funktion bestehen bleibt.

➜ Die Zuweisung eines Zeigers (abc = (char*)"Test1234";) ändert nur die lokale Kopie des Zeigers.
strcpy() ändert den Inhalt des Speichers, auf den der Zeiger zeigt, was eine bleibende Wirkung im Hauptprogramm hat.

1 Like

Hallo J-M-L,

vielen Dank für die schnelle Antwort - das habe ich verstanden! Gibt es da eine Möglichkeit, ohne "strcpy" den Inhalt des Arrays zu ändern? Man müsste doch nur irgendwie ausdrücken, das keine Lokaler Pointer erzeugt wird.

Gruß,
Horst

Es gibt andere Funktionen zum Füllen des Speichers (wie memcpy, ...), aber in jedem Fall läuft es darauf hinaus, eine Schleife zu machen, die die Bytes von einer Seite nimmt, um sie auf die andere Seite zu kopieren.

1 Like

Super Vielen Dank!

Dann kann ich das strcpy lassen. Ist vielleicht auch besser erst mal das Array lokal aufzuarbeiten und am Schluss die Kopie des ganzen wieder zu übernehmen.

In der ersten Variante bekomme ich Warnungen.

Dein Programm
void test (char *abc) {
  char *abcde = (char*)"Test1234";
  Serial.println (abcde);
  strcpy(abc, abcde);      // Inhalt von abcde nach abc kopieren
  Serial.println (abc);    // Ausgabe: "Test1234"
}

void setup()
{
  Serial.begin(9600);
  Serial.println("\nStart");
  char *xyz;  // oder char xyz[256];
  char *uvw;
  xyz = (char*)"Hallo";
  uvw = (char*)"ABCDEFGHIJ";
  Serial.println(xyz);
  Serial.println(uvw);
  test (xyz);
  Serial.println(xyz);
  Serial.println(uvw);
}

In function 'test.constprop',
inlined from 'setup' at 18:8,
inlined from 'main' at C:\Program Files (x86)\Arduino-1.8.19\hardware\arduino\avr\cores\arduino\main.cpp:43:7:
4:9: warning: 'strcpy' writing 9 bytes into a region of size 6 overflows the destination [-Wstringop-overflow=]
strcpy(abc, abcde); // Inhalt von abcde nach abc kopieren
^
Der Sketch verwendet 1614 Bytes (5%) des Programmspeicherplatzes. Das Maximum sind 32256 Bytes.
Globale Variablen verwenden 220 Bytes (10%) des dynamischen Speichers, 1828 Bytes für lokale Variablen verbleiben. Das Maximum sind 2048 Bytes.

Diese Warnung darfst Du nicht ignorieren, da nachfolgender Speicher überschrieben wird!

Tut nicht, was Du willst, erzeugt aber dank strncpy auch keinen Laufzeitfehler:

Programm mit strncpy
void test (char *abc, const size_t len) 
{
  char *abcde = (char*)"Test1234";
  Serial.println (abcde);
  strncpy(abc, abcde, len);  // Inhalt von abcde nach abc kopieren
  Serial.println (abc);      // Ausgabe: "Test1234"
}

void setup()
{
  Serial.begin(9600);
  Serial.println("\nStart");
  char *xyz;  // oder char xyz[256];
  char *uvw;
  xyz = (char*)"Hallo";
  uvw = (char*)"ABCDEFGHIJ";
  Serial.println(xyz);
  Serial.println(uvw);
  test (xyz, strlen(xyz));
  Serial.println(xyz);
  Serial.println(uvw);
}

void loop() {}
1 Like

Pointer sind böse.
Und wenn nötig, sind sie immer noch ein Übel.

So fängt es an!

E:\Programme\arduino\portable\sketchbook\sketch_dec29e\sketch_dec29e.ino: In function 'void test(char*&)':
E:\Programme\arduino\portable\sketchbook\sketch_dec29e\sketch_dec29e.ino:7:17: warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]
    7 |   char *abcde = "Test1234";
      |                 ^~~~~~~~~~

Und so endet es dann im "undefined behavier":

In function 'test',
    inlined from 'setup' at E:\Programme\arduino\portable\sketchbook\sketch_dec29e\sketch_dec29e.ino:23:9,
    inlined from 'main' at E:\Programme\arduino\portable\packages\arduino\hardware\avr\1.8.6\cores\arduino\main.cpp:43:7:
E:\Programme\arduino\portable\sketchbook\sketch_dec29e\sketch_dec29e.ino:9:9: warning: 'strcpy' writing 9 bytes into a region of size 6 overflows the destination [-Wstringop-overflow=]
    9 |   strcpy(abc, abcde);      // Inhalt von abcde nach abc kopieren
      |         ^

1 Like

Vielen Dank nochmal für eure Unterstützung!

Der Tip mit strncpy ist natürlich auch gut! Ich hätte hier noch vor dem strcpy eine Abfrage eingebaut, damit das nicht passiert. Aber so ist es natürlich perfekt!

Mir ging es auch in erster Linie nur darum, warum das mit strcpy geht und andersrum nicht.

Den Rest... Naja da kann ich mich jetzt nicht raus reden. War halt schnell dahin getippt und wenn man einen Fehler hat, probiert man halt auch mal das eine oder andere aus und vergisst dann auch mal ein (*char). Aber trotzden gut, das darauf hingewiesen wurde, wenn jemand den Thread mal öffnet, weiß er wenigstens wie es richtig geht :wink:

Viele Grüße,
Horst

Keine Ahnung, was das ist!
Oder sein soll.

Nicht wirklich!
Das ist alles noch weit vom Ideal entfernt.
Immer noch mit gefährlichem Fehlerpotential.

Aber u.U. Fehler zur Laufzeit. :japanese_ogre: :japanese_ogre:

If count is reached before the entire string src was copied, the resulting character array is not null-terminated.

Evtl. strcpy, auch wenn es nicht zu Standard gehört.

Gruß Tommy

Deine freundlich dreinblickenden japanischen Masken lassen mich das Problem nicht erkennen. strlen zählt die Zeichen ohne Endezeichen '\0', weshalb das vorhandene Endezeichen im Ziel bei strncpy nicht überschrieben werden kann. Daher dürfte nach meinem Verständnis der zitierte Satz auch keine Auswirkung haben.

Oder?

Bei strlen(xyz) + 1 geht es schief, was nicht verwundert.

Programm
#include <Streaming.h>      // https://github.com/janelia-arduino/Streaming

void test (char *abc, const size_t len) 
{
  char *abcde = (char*)"Test1234";
  Serial << "abcde: " << abcde << endl;
  strncpy(abc, abcde, len);  // Inhalt von abcde nach abc kopieren
  Serial << "  abc: " << abc << endl;      // Ausgabe: "Test1234"
}

void setup()
{
  Serial.begin(9600);
  Serial << "\nStart\n";
  char *xyz = (char*)"Hallo";
  Serial << "1 xyz: " << xyz << endl;
  test (xyz, strlen(xyz));
  Serial << "2 xyz: " << xyz << endl;
  test (xyz, strlen(xyz) + 1);
  Serial << "3 xyz: " << xyz << endl;
}

void loop() {}
Anzeige

Start
1 xyz: Hallo
abcde: Test1234
abc: Test1
2 xyz: Test1
abcde: Test1234
abc: Test12 abc:
3 xyz: Test12 abc:

Ja, nee...

Das Problem wohnt eine Schicht tiefer.
Redundanzen!
Und diese öffnen die Tür für copy&paste, Tippfehler und sonstige kleine Irrtümer.

Dass solche Irrtümer passieren können sieht man hier:

warning: 'strcpy' writing 9 bytes into a region of size 6 overflows the destination

Das führt zum Abschmierer des Programms, wg. UB
Das (hier) wird zum Glück bemerkt und gewarnt.
Aber das ist längst nicht immer so.
Dann sind solche Fehler recht schwer zu finden.

In C gab es keine Alternative, als die Länge explizit mit zu übergeben.
In C++ haben wir mehr Möglichkeiten, dieser Falle aus dem Wege zu gehen.

Auch das hier halte ich für gefährlich:

Unangenehme Seiteneffekte drohen.
Siehe:

#include <Streaming.h>      // https://github.com/janelia-arduino/Streaming

void test (char *abc, const size_t len) 
{
  char *abcde = (char*)"Test1234";
  Serial << "abcde: " << abcde << endl;
  strncpy(abc, abcde, len);  // Inhalt von abcde nach abc kopieren
  Serial << "  abc: " << abc << endl;      // Ausgabe: "Test1234"
}

void setup()
{
  Serial.begin(9600);
  Serial << "\nStart\n";
  char *xyz = (char*)"Hallo";
  Serial << "1 xyz: " << xyz << endl;
  test (xyz, strlen(xyz));
  Serial << "2 xyz: " << xyz << endl;
  test (xyz, strlen(xyz) + 1);
  Serial << "3 xyz: " << xyz << endl;
  Serial << "4 Hallo: " << "Hallo" << endl;
}

void loop() {}

Ausgabe:

Start
1 xyz: Hallo
abcde: Test1234
  abc: Test1
2 xyz: Test1
abcde: Test1234
  abc: Test12  abc: 
3 xyz: Test12  abc: 
4 Hallo: Test12  abc:

Unstimmigkeiten finden sich hier!

  1. 4 Hallo: Test12 abc: Wo ist das Hallo geblieben?

:japanese_ogre: :japanese_ogre:

Natürlich!

Das schmeckt mir alles nicht wirklich! (das Pointergehampel)
Mag sein, dass dir keine bis wenig Irrtümer passieren.
Mir allerdings schon einige.
Da hinterher suchen zu wollen, fehlt mir der Antrieb. Da mache ich es doch lieber im Vorfeld, mit etwas Disziplin, richtig

Wer sich für unfehlbar hält, macht den ersten Fehler!

Programm
#include <Streaming.h>      // https://github.com/janelia-arduino/Streaming

void test (char *abc, const size_t len) 
{
  char *abcde = (char*)"Test1234";
  Serial << "abcde: " << abcde << endl;
  strncpy(abc, abcde, len);  // Inhalt von abcde nach abc kopieren
  Serial << "  abc: " << abc << endl;
}

void setup()
{
  Serial.begin(9600);
  Serial << "\nStart\n";
  char *xyz = (char*)"verlorener Text";                                // ←
  char *uvw = (char*)"X";
  Serial << "1 xyz: " << xyz << endl;
  test (xyz, strlen(xyz));
  Serial << "2 xyz: " << xyz << endl;
  Serial << "3 Hallo: " << uvw << "verlorener Text" << uvw << endl;    // ←
  Serial << "4 Hallo: " << uvw << "verlorener Text 2" << uvw << endl;
  Serial << "4 Hallo: " << uvw << "verlorener Text" << uvw << endl;    // ←
}

void loop() {}
Anzeige

Start
1 xyz: verlorener Text
abcde: Test1234
abc: Test1234
2 xyz: Test1234
3 Hallo: XTest1234X
4 Hallo: Xverlorener Text 2X
4 Hallo: XTest1234X

Wenn Du von Redundanz schreibst, wird "verlorener Text" eventuell nur einmal mit mehreren Zeigern gespeichert. Wird der Text verändert, sieht auch der zweite Zeiger den veränderten Inhalt. Nach dem Fehler möchte ich nicht suchen müssen!

Bei mir gekennzeichnet mit Bitte nicht nachmachen!

Danke! Dir wünsche ich fleißige und gesunde Bienen in 2025!

@tubie1977: Diese Verwendung der Zeiger solltest Du nochmal überdenken!

Im Grunde meine ich dieses:

Erster, Parameter ist ein Pointer
Zweiter, die Länge
Leider geht beim Umwandeln von einem Char Array in einen Pointer die wahre Array Länge/Größe verloren.
Wenn irgend die Chance besteht, sollte man das Array übergeben, denn dessen Datentype enthält die Array Länge/Größe. Zumindest kann man sie aus dem Datentype ermitteln. Tut man das, gibt es eine Redundanz weniger. Eine potentielle Fehlerquelle weniger.

Ja, eine dokumentierte Optimierung.

#include <Streaming.h>      // https://github.com/janelia-arduino/Streaming

void setup()
{
  Serial.begin(9600);
  Serial << "\nStart\n";
  char *a  = (char*)"Der braune Frosch";
  char *b  = (char*)"Frosch";
  char c[] = "Der braune Frosch";

  Serial << "1a: " << a << endl;
  Serial << "1b: " << b << endl;
  Serial << "1c: " << c << endl;
  b[0] = 'X';
  Serial << "2a: " << a << endl;
  Serial << "2b: " << b << endl;
  Serial << "2c: " << c << endl;
}

void loop() {}

Ausgabe:

Start
1a: Der braune Frosch
1b: Frosch
1c: Der braune Frosch
2a: Der braune Xrosch
2b: Xrosch
2c: Der braune Frosch

Mein Rat:
Zeiger, wenn es geht, verzichten.
C Type Casts, ebenso.

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.