Forum: PC-Programmierung Call by Value, char-Arrays


von Michi (Gast)


Lesenswert?

Hallo Zusammen,
ich habe zu den Themen Call by Value/Stack und "char-Arrays" unter C++ 
jeweils eine Frage, für die es zwar andere Lösungen gibt, jedoch geht es 
mir um das Verständnis.

1. Wie genau funktioniert Call by Value, da in Büchern oft nicht exakt 
beschrieben wird, wie es funktioniert. Wir bei Aufruf einer Funktion der 
Wert des zu übergebenden Parameters an einer bestimmten Adresse im Stack 
gespeichert und von der Funktion selbst an einer anderen Adresse, wo 
eine Instanz erzeugt wird, der Wert sozusagen vom Stack kopiert?

2. Ich kann char-Arrays deklarieren und definieren:
char Name[30] = "Test";

Getrennt funktioniert das aber nicht:
char Name[30];
Name[] = "Test";

Woran liegt das? Mir ist klar, dass eine Zuweisung nur wie folgt möglich 
ist:
Name[0] = 'T';
Name[1] = 'e';
Name[2] = 's';
Name[3] = 't';

Aber wieso ist eine Zuweisung des Strings "Test" direkt bei der 
Deklaration möglich, später jedoch nur, wenn die einzelnen Elemente [] 
angesprochen werden?  Speicher wird mit oder ohne String bei der 
Deklaration reserviert, also spielt das zumindest keine Rolle bei der 
Zuweisung.

Danke vorab für die Mühe.

von Michi (Gast)


Lesenswert?

Andersherum kann ich meine char Name[30] innerhalb einer Struktur nicht 
initialisieren:
1
struct S_Spieler{
2
  int Energie;
3
  int Punkte;
4
  int xPosition;
5
  int yPosition;
6
  char Name[30]="Test";   //Nicht möglich, obwohl Speicherplatz reserviert wird
7
};

von Dr. Sommer (Gast)


Lesenswert?

Michi schrieb:
> 1. Wie genau funktioniert Call by Value, da in Büchern oft nicht exakt
> beschrieben wird, wie es funktioniert.
Das hängt von der Plattform und Calling Convention ab und kann daher 
nicht allgemein beantwortet werden, deswegen steht das da nicht. Es wird 
eine Kopie des Parameters an die Funktion übergeben, welche dann diese 
Kopie bearbeiten kann. Wenn der Parameter ein Pointer oder eine Referenz 
ist, wird eben der Pointer/Referenz kopiert, aber nicht das Ziel.

Michi schrieb:
> Wir bei Aufruf einer Funktion der
> Wert des zu übergebenden Parameters an einer bestimmten Adresse im Stack
> gespeichert
Bei vielen Plattformen werden die ersten Parameter in Prozessor-Register 
kopiert und der Rest auf den Stack.

Michi schrieb:
> und von der Funktion selbst an einer anderen Adresse, wo
> eine Instanz erzeugt wird, der Wert sozusagen vom Stack kopiert?
Was für eine Instanz? Die Funktion nutzt die Werte vom Stack/Register 
direkt, ohne Kopie.

Michi schrieb:
> Woran liegt das? Mir ist klar, dass eine Zuweisung nur wie folgt möglich
Man kann Arrays in C halt nicht direkt kopieren. Das wurde vermutlich so 
entschieden weil das eine Schleife beinhalten würde, und C Entwickler 
mögen keine versteckten aufwändigen Operationen. In C++ wurde das 
Verhalten auf den Built-In-Typen größtenteils übernommen, weswegen da 
auch so ist.
Wenn du so etwas machen möchtest, nutze std::array oder std::vector oder 
std::string.

von Vlad T. (vlad_tepesch)


Lesenswert?

Michi schrieb:
> struct S_Spieler{
>   int Energie;
>   int Punkte;
>   int xPosition;
>   int yPosition;
>   char Name[30]="Test";   //Nicht möglich, obwohl Speicherplatz
> reserviert wird
> };

dann hast du nen ziemlich angestaubten C++ Compiler.

1
struct Test {
2
  char name[20] = "Default";
3
  int i = 5;
4
  float f = 4.f;
5
6
};
meiner übersetzt sowas ohne zu zucken


Davon abgesehen, sieht das, was du da anstellst relativ wenig nach C++ 
aus.
Da würde man
1
std::string Name;
benutzen.
Da gibts auch gleich die passenden Konstruktoren für string-Literale;

von Michi (Gast)


Lesenswert?

char name[20] = "Default";

Eigentlich ist es nicht richtig, dass der Compiler es übersetzt, da eine 
Struktur nur Deklarationen enthalten sollte. Die Initalisierung sollte 
immer mit den jeweiligen Instanzen erfolgen.

Und was Dr. Sommer schrieb: "Was für eine Instanz? Die Funktion nutzt 
die Werte vom Stack/Register direkt, ohne Kopie."

Die Funktion nutzt zwar Werte, jedoch wird die Variable beim Aufruf 
nicht direkt übergeben sondern vielmehr eine Kopie, die nach dem Beenden 
der Funktion ihre Gültigkeit verliert.

von Peter II (Gast)


Lesenswert?

Michi schrieb:
> Andersherum kann ich meine char Name[30] innerhalb einer Struktur nicht
> initialisieren:struct S_Spieler{
>   int Energie;
>   int Punkte;
>   int xPosition;
>   int yPosition;
>   char Name[30]="Test";   //Nicht möglich, obwohl Speicherplatz
> reserviert wird
> };

hier wird kein Speicher reserviert. Das legt nur eine Struct an, es gibt 
noch keine passende Variable.

von Mikro 7. (mikro77)


Lesenswert?

Michi schrieb:
> Eigentlich ist es nicht richtig, dass der Compiler es übersetzt, da eine
> Struktur nur Deklarationen enthalten sollte. Die Initalisierung sollte
> immer mit den jeweiligen Instanzen erfolgen.

Eine struct ist eine public class. In-class init ist ab C++11 möglich.

> Und was Dr. Sommer schrieb: "Was für eine Instanz? Die Funktion nutzt
> die Werte vom Stack/Register direkt, ohne Kopie."

Das ist die Kopie.

Michi schrieb:
> Woran liegt das?

C-style Array werden (überwiegend) wie Adressen behandelt. 
Dementsprechend ist call-by-value nicht möglich. Du kannst allerdings 
ein Array in eine struct einbetten und die struct by-value übergeben 
(siehe std::array in C++).

von Vlad T. (vlad_tepesch)


Lesenswert?

Michi schrieb:
> char name[20] = "Default";
>
> Eigentlich ist es nicht richtig, dass der Compiler es übersetzt, da eine
> Struktur nur Deklarationen enthalten sollte. Die Initalisierung sollte
> immer mit den jeweiligen Instanzen erfolgen.

Natürlich erfolgt die Initialisierung bei der Instanzierung. Vorher ist 
ja kein Speicher da.

Nur ist das die Moderne Variante in C++ Member zu initialisieren. Damit 
vermeidet man in jedem Konstruktor die selben Werte schreiben zu müssen, 
die Gefahr laufen, bei Änderung vergessen zu werden.
Außerdem sind so die Defaultwerte schön sichtbar neben der Definition.
Außerdem spart man sich so oft, überhaupt einen Default-Konstruktor 
schreiben zu müssen.

Für primitive Typen gibts das seit c++11. Mit c++14 sind nochmal die 
Regeln, wann das geht erleichtert worden.

: Bearbeitet durch User
von Michi (Gast)


Lesenswert?

Peter II schrieb:
> S_Spieler

Doch, wenn ich per sizeof(S_Spieler) Aufrufe, ist Speicher reserviert, 
obwohl noch keine Instanz erzeugt.

von Peter II (Gast)


Lesenswert?

Michi schrieb:
> Doch, wenn ich per sizeof(S_Spieler) Aufrufe, ist Speicher reserviert,
> obwohl noch keine Instanz erzeugt.

nein, siezeof liefert die größe, sagt aber überhaupt nicht ob Speicher 
reserviert ist.

von Michi (Gast)


Lesenswert?

Vlad T. schrieb:
> Für primitive Typen gibts das seit c++11. Mit c++14 sind nochmal die
> Regeln, wann das geht erleichtert worden.

Ich nutze den Visual Studio Express 2012 Compiler. Daher wird das wohl 
noch nicht unterstützt und ich kann die Initialisierung erst nach der 
Deklaration einer Instanz durchführen.

Ich habe dazu noch eine andere Frage. Wenn ich eine Variable einer 
Struktur über eine Funktion ändern möchte, ist es dann sinnvoll die 
komplette Struktur per Zeiger zu übergeben und dann in der FUnktion die 
entsprechende Variable per "STRUKTUR->Punkte=293 .... " zu ändern oder 
kann ich auch einfach nur diese Variable übergeben? Funktionieren tut 
beides.

Aufruf:
BerechnePunkte(&Spieler1, Bonus);

Funktion:
void BerechnePunkte(S_Spieler Spielertemp, int Bonus)
------------------
Das hier geht auch:
Aufruf:
BerechnePunkte(&Spieler1.Punkte, Bonus);

Funktion:
void BerechnePunkte(int *p_Punkte, int Bonus)
-------------------
Denn wenn ich mir in der Mainfunktion eine Instanz erzeuge, besitzt die 
Strukturvariable "Punkte" bereits eine Adresse. Also kann ich die 
Parameterübergabe per Zeiger auch nur für diese eine Variable machen. 
Dennoch wird meistens die komplette Struktur per Zeiger übergeben, 
warum?

von Vlad T. (vlad_tepesch)


Lesenswert?

Michi schrieb:
> Ich nutze den Visual Studio Express 2012 Compiler.

dann nutze doch einen neueren.
Wenn du dich sowieso in C++ einarbeitest, dann nutze doch wenigstens 
eine aktuelle Version davon.

Michi schrieb:
> Ich habe dazu noch eine andere Frage. Wenn ich eine Variable einer
> Struktur über eine Funktion ändern möchte, ist es dann sinnvoll die
> komplette Struktur per Zeiger zu übergeben und dann in der FUnktion die
> entsprechende Variable per "STRUKTUR->Punkte=293 .... " zu ändern oder
> kann ich auch einfach nur diese Variable übergeben? Funktionieren tut
> beides.

Das ist immer eine Frage des Kontextes.
Wenn die Informationen, die du in der Funktion brauchst, alle auch ohne 
das Objekt da sind, könnte man auch über einen einfachen Rückkagebewert 
nachdenken.
Zeiger verwendet man in c++ nicht ganz so oft (eigentlich nur, wenn man 
optionale by-referenze Argumente braucht) Ansonsten sollte man 
Referenzen benutzen.

> void BerechnePunkte(S_Spieler Spielertemp, int Bonus)
wird nicht funktionieren, oder ist S_Spieler ein Pointer?
Falls ja: sowas ist schlechter Stil. Seperate Pointer-Typ-Definitionen 
vermüllen meist nur den Namensraum und machen den Code unübersichtlich, 
weil man beim Lesen das S_Spieler erst in Spieler* übersetzen muss.
1
void BerechnePunkte(S_Spieler& Spielertemp, int Bonus)


Man sollte vielleicht auch überlegen, zu wem diese Funktion gehört.
Ist es vielleicht eine Memberfunktion von Spieler? dann sollte sie so 
aussehen:
1
class Spieler{
2
void BerechnePunkte(int Bonus)
3
// ...
4
}

oder gehört sie eher in die "Game"-Klasse.
Die kennt ja in der Regel enthaltenen Spieler. hier könnte der Parameter 
also ein Index, oder eine Name sein, über den die Game-Klasse die 
Spieler referenziert.

Ich hab das Gefühl bei deinem derzeitigem Wissensstand wäre es 
sinnvoller, du suchst dir ein modernes C++ Tutorial und arbeitest das 
erst mal durch (zusammen mit einem aktuellerem Compiler, wie VS2015 oder 
VS2017).

Vielleicht findest du auch ein nettes Tutorial über Objektorientiertes 
Design

: Bearbeitet durch User
von Carl D. (jcw2)


Lesenswert?

Ob der VSE2012 Compiler C++11 schon unterstützt, weiß ich nicht, aber 
selbst wenn, müsste man ihn erst per Commandline-Parameter darum bitten. 
Eine typische Aktion bei mir nach dem ersten Compile eines C++-Projekts 
mit Eclipse/GCC: -std=C++14 in die Projektsettings eintragen.

von Hans (Gast)


Lesenswert?

Carl D. schrieb:
> Ob der VSE2012 Compiler C++11 schon unterstützt, weiß ich nicht,
> aber
> selbst wenn, müsste man ihn erst per Commandline-Parameter darum bitten.
> Eine typische Aktion bei mir nach dem ersten Compile eines C++-Projekts
> mit Eclipse/GCC: -std=C++14 in die Projektsettings eintragen.

Nein, muss man nicht. So einen Schalter gibt es erst seit kurzem:
https://blogs.msdn.microsoft.com/vcblog/2016/06/07/standards-version-switches-in-the-compiler/

Hier gibt es übrigens eine Übersicht der unterstützten C++-Features je 
nach Version:
https://msdn.microsoft.com/en-us/library/hh567368(v=vs.140).aspx

Non-static data member initializers gibt es erst ab VS 2013.

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.