PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Schlechter Stil?


Gast
2011-02-09, 10:56:34
Hallo!

Ich bringe mir gerade C/C++ bei und spiele momentan mit Funktionen und Übergabeparametern rum.
Nun meine ich gelernt zu haben, dass man z.B. für die Übergabe von einem char-Array Zeiger verwendet, also z.B.:


bool useContent(char *content)
{
do ...
return true/false;
}

void main()
{
char text[] = "abcdefg";
useContent(text);
}


Wenn ich nun weiß, dass ich immer ein Array der gleichen Länge übergebe, würde man es dann als "schlechten Stil" bezeichnen, wenn ich anstelle des Zeigers ein Array fester Größe übergeben würde, also z.B.:


bool useContent(char content[10])
{
do ...
return true/false;
}

void main()
{
char text[10] = "abcdefghi";
useContent(text);
}

Ectoplasma
2011-02-09, 11:19:22
Der Stil ist ok, aber du solltest das Array als Referenz oder Pointer übergeben, sonst kopiert der Compiler unter Umständen den kompletten Inhalt an den Parameter "content". Wenn die Größe des Arrays ein unabdinbarer Bestandteil deines Datentyps ist, dann würde ich den Typ noch genauer spezifizieren, indem du ein Typedef machst.


typedef char MyDataType[10];

...

bool useContent(const MyDataType &content)
{
do ...
return true/false;
}


Eine Sache ist noch. Falls du den Inhalt von "content" modifiziern möchtest, dann musst du "const" aus "useContent" Signatur heraus nehmen. Der Stil wäre aber dann nicht mehr ganz so schön, weil die Funktion "useContent" dann einen Rückgabewert hat und gleichzeitig könnte als Seiteneffekt der Inhalt von "content" modifiziert werde. Allerdings wäre dieses Vorgehen wiederum gängige Praxis.

Gast
2011-02-09, 11:31:01
Vielen Dank!
Am "schönsten" wäre also meine erste Variante mit dem Zeiger (ist die so auch komplett richtig?) oder deine Variante mit dem typedef?

Gast
2011-02-09, 11:39:57
Das bringt mich noch zu einer weiteren Frage..wieso ist so etwas möglich?

char *text = new char[10];
text = "abcdefghijklmnop";

cout << text << endl;

Ich reserviere 10 Byte für "text", kann aber - ohne dass der Compiler meckert - mehr hineinschreiben und ausgeben. Überseh ich dabei irgendwas?

Senior Sanchez
2011-02-09, 11:50:13
Das bringt mich noch zu einer weiteren Frage..wieso ist so etwas möglich?

char *text = new char[10];
text = "abcdefghijklmnop";

cout << text << endl;

Ich reserviere 10 Byte für "text", kann aber - ohne dass der Compiler meckert - mehr hineinschreiben und ausgeben. Überseh ich dabei irgendwas?

Das ist schon gefährlich. ;) Denn eventuell schreibst du dann in Speicherbereiche rein, in die du nicht reinfummeln solltest. Funktionieren tut es, da cout den Inhalt bis zur Stringterminierung schreibt.

Krishty
2011-02-09, 12:03:08
Der Stil ist ok, aber du solltest das Array als Referenz oder Pointer übergeben, sonst kopiert der Compiler unter Umständen den kompletten Inhalt an den Parameter "content".Hast du dafür zufällig einen Paragraphen im Standard parat? Ich bin mir sicher, dass Arrays immer by-reference übergeben werden (finde den entsprechenden Abschnitt aber gerade nicht).

Könnten Arrays by-value übergeben werden, wäre alle mir bekannten Compiler fehlerhaft weil sich dort Änderungen an Array-Parametern global auswirken … ganz zu schweigen von der dadurch höheren Komplexität, weil man, um das zu erreichen, ja beim Verlassen der Funktion – egal, ob durch return oder throw – auch das veränderte Array an die Ursprungsadresse zurückkopieren müsste.

Exxtreme
2011-02-09, 12:03:27
Ich reserviere 10 Byte für "text", kann aber - ohne dass der Compiler meckert - mehr hineinschreiben und ausgeben. Überseh ich dabei irgendwas?
Ja, das geht und der Compiler wird nicht meckern. Damit hast du einen sog. Buffer overflow (http://de.wikipedia.org/wiki/Puffer%C3%BCberlauf) verursacht. Diese Buffer overflows sind für rund 50% aller Sicherheitslücken verantwortlich.

Krishty
2011-02-09, 12:08:37
Das ist schon gefährlich. ;) Denn eventuell schreibst du dann in Speicherbereiche rein, in die du nicht reinfummeln solltest. Funktionieren tut es, da cout den Inhalt bis zur Stringterminierung schreibt.Quatsch. Er weist dem Zeiger einfach nur eine neue Adresse zu (die, an der der String im Programmdatensatz abgelegt ist). Da wird nichts überschrieben außer dem Zeiger. Wenn er delete drauf aufrufen will, dann kracht es – weil er nun eine Adresse des Datensegments übergibt und der allokierte Speicher längst verloren ist.

Coda
2011-02-09, 12:10:13
Korrekt. Krishty hat recht.

Gast
2011-02-09, 12:10:45
Lässt sich das irgendwie automatisch verhindern oder muss ich mich da immer "manuell" drum kümmern?

Krishty
2011-02-09, 12:13:29
new ohne Smart-Pointer zu benutzen ist eine Artikulation des Willens, sich manuell drum kümmern zu wollen, also ja.

Gast
2011-02-09, 12:51:35
new ohne Smart-Pointer zu benutzen ist eine Artikulation des Willens, sich manuell drum kümmern zu wollen, also ja.

Schön gesagt :D. Danke für den Hinweis, von Smart-Pointern hab ich noch nie was gehört.

Gast
2011-02-09, 13:32:11
So, das ist denk ich die letzte doofe Frage für Heute :):

char *text;
text = "abcdefghi";

char *text2 = new char[10];
text2 = "abcdefghi";

Beides funktioniert (zumindest wenn ich text/text2 mit cout ausgebe) aber wo liegt nun der Unterschied?

Exxtreme
2011-02-09, 13:42:26
Der Unterschied ist, auf einem ordentlichen Betriebssystem stürzt das erste Beispiel ab. :D

Krishty
2011-02-09, 13:45:17
Beim ersten Mal allokierst du zehn Bytes, die futtsch sind sobald du den Zeiger auf den frischen Speicher mit einem Zeiger auf den String überschreibst.

Der String ist eine Konstante, die bei der Kompilierung in die Exe geschrieben wird. Und die Exe wird bei der Ausführung in den Speicher übertragen. Darum hat auch der String eine Adresse im Speicher. Und die weist du deinem Zeiger zu. Du weist bloß Zeiger zu; außer diesen vier Bytes (oder acht auf 64-Bit-Systemen) wird da überhaupt nichts rumkopiert.

Der Unterschied ist, auf einem ordentlichen Betriebssystem stürzt das erste Beispiel ab. :DFalsch, das Programm ist – abgesehen von dem Speicherleck durch new – einwandfrei. Wurde oben bereits zweimal gesagt.

Netzzwerg
2011-02-09, 13:48:16
Falsch, das Programm ist – abgesehen von dem Speicherleck durch new – einwandfrei.

Also würdest du "new" immer vermeiden und in seinem Fall

char *text;
text = "abcdefghi";

verwenden?

DocEW
2011-02-09, 13:48:52
Beim ersten Mal allokierst du zehn Bytes, die futtsch sind sobald du den Zeiger auf den frischen Speicher mit einem Zeiger auf den String überschreibst.
Was genau heißt "futtsch"? Das ist ein Speicherleck, oder?

Markus89
2011-02-09, 13:50:30
Was genau heißt "futtsch"? Das ist ein Speicherleck, oder?
Ja, ist es. Wenn man new benutzt hat, sollte man es mit delete wieder löschen, was in diesem Fall nicht mehr geht.

Krishty
2011-02-09, 13:51:07
Also würdest du "new" immer vermeiden und in seinem Fall

char *text;
text = "abcdefghi";

verwenden?Oder gleich
char const * text = "abcdefghi";
(Das const ist relativ wichtig, da der String nicht veränderbar ist. Man darf da nicht reinschreiben, sonst stürzt es tatsächlich ab. Dass auch non-const geht, ist Abwärtskompatibilität zu C.)

Was genau heißt "futtsch"? Das ist ein Speicherleck, oder?Genau.

Krishty
2011-02-09, 13:57:41
Nochmal um die Sache zu klären: Das hier legt einen Zeiger auf einen nicht veränderbaren String an, der im Datensegment liegt:

char const * text = "abcd";

Und das hier legt einen veränderbaren String an, der auf dem Heap liegt und "abcd" enthält:

char * text = new char[strlen("abcd")];
strcpy(text, "abcd");

Die zweite Variante muss auch wieder per delete text; freigegeben werden. Und darum solltet ihr alle std::string benutzen.

Gast
2011-02-09, 14:03:27
Klar werde ich auch sonst std::string benutzen, mir ging es jetzt nur mal um die Basics :). Merci!

Gast
2011-02-09, 14:10:39
Jetzt muss ich doch nochmal was nachhaken:

char const * text = "abcd";
cout << text << endl; // gibt aus -> abcd

text = "abc";
cout << text << endl; // gibt aus -> abc

Wieso kann ich den String ändern?

Krishty
2011-02-09, 14:13:27
Kannst du nicht. "abcd" und "abc" liegen an unterschiedlichen Stellen; haben unterschiedliche Adressen. Du überschreibst nur den Zeiger. Statt auf den Datenbereich mit "abcd" zeigt er danach eben auf den mit "abc".

Stell es dir als

int abcd;
int abc;
int * text = &abcd;
text = &abc;

vor.

Gast
2011-02-09, 14:22:51
Ach klar, Zeiger machen mich immer noch ganz wuschig :D. Nochmals vielen Dank für deine Geduld!

Exxtreme
2011-02-09, 14:43:18
Falsch, das Programm ist – abgesehen von dem Speicherleck durch new – einwandfrei. Wurde oben bereits zweimal gesagt.
Recht hast du. Ich habe schon vergessen, dass ein Array schon selbst ein Zeiger ist. Java versaut mich langsam. :D

Senior Sanchez
2011-02-09, 14:51:56
Quatsch. Er weist dem Zeiger einfach nur eine neue Adresse zu (die, an der der String im Programmdatensatz abgelegt ist). Da wird nichts überschrieben außer dem Zeiger. Wenn er delete drauf aufrufen will, dann kracht es – weil er nun eine Adresse des Datensegments übergibt und der allokierte Speicher längst verloren ist.

Hast Recht, da habe ich mich verguckt, da ich dachte, dass der Zeiger derefenziert wird. Hat er aber nicht und demzufolge bekommt der Zeiger natürlich eine neue Adresse zugewiesen.

Ich wills jetzt nicht ausprobieren, aber was passiert wenn er den Zeiger dereferenziert und dann diese Zuweisung macht? Der Compiler sollte da nicht meckern, aber da könnte er wohl über den allokierten Speicher hinaus schreiben, oder?

Krishty
2011-02-09, 19:35:48
Recht hast du. Ich habe schon vergessen, dass ein Array schon selbst ein Zeiger ist. Java versaut mich langsam. :DIst es nicht; es lässt sich nur implizit zu einem Zeiger konvertieren ;)

Ich wills jetzt nicht ausprobieren, aber was passiert wenn er den Zeiger dereferenziert und dann diese Zuweisung macht? Der Compiler sollte da nicht meckern, aber da könnte er wohl über den allokierten Speicher hinaus schreiben, oder?Falls wir noch von
char * text = "abcd";
mit entsprechendem
strcpy(text, "inspector Very Long and Even Longer");
sprechen: Das kommt auf die Definition von „allokiert“ an ;) Das String-Literal ist im Datensegment abgelegt …

Wenn der Compiler das als schreibgeschützt markiert hat, ist jede Schreiboperation eine Schutzverletzung. (So ist es seit Jahren unter Windows; anderswo kA.)

Wenn nicht, kann man reinschreiben. Hinter dem String "abcd" muss nicht zwangsläufig sofort eine neue Seite liegen, so dass auch keine Schutzverletzung auftreten muss, wenn man drüber hinausschreibt. Theoretisch und mit viel Glück würde man schlicht und einfach alle String-Literale des Programms überschreiben können (inklusive dem Literal, das man strcpy() übergibt).

Senior Sanchez
2011-02-09, 20:51:50
Ich meinte eher so etwas wie

char *text = new char[10];
*text = "abcdefghijklmnop";

cout << text << endl;


Wenn nicht, kann man reinschreiben. Hinter dem String "abcd" muss nicht zwangsläufig sofort eine neue Seite liegen, so dass auch keine Schutzverletzung auftreten muss, wenn man drüber hinausschreibt. Theoretisch und mit viel Glück würde man schlicht und einfach alle String-Literale des Programms überschreiben können (inklusive dem Literal, das man strcpy() übergibt).

Das das nicht immer zu einem Fehler führt, dachte ich mir. Aber so tief stecke ich da nicht in den Details drin. Von daher danke für die Erläuterung. :)

Krishty
2011-02-09, 20:53:31
Ich meinte eher so etwas wie

char *text = new char[10];
*text = "abcdefghijklmnop";

cout << text << endl;Das kompiliert nicht – die linke Seite der Zuweisung hat den Typ char, die rechte den Typ char[17].

Das das nicht immer zu einem Fehler führt, dachte ich mir.Naja, unter Windows schon. Woanders weiß ich es nicht. Darum so viel „theoretisch“ und „falls“ im Text ;)

Senior Sanchez
2011-02-09, 20:59:28
Das kompiliert nicht – die linke Seite der Zuweisung hat den Typ char, die rechte den Typ char[17].

Naja, unter Windows schon. Woanders weiß ich es nicht. Darum so viel „theoretisch“ und „falls“ im Text ;)

Stimmt, ja. Habs gerade selber ausprobiert und ist natürlich logisch. ;)
Ich bleib einfach weiter fröhlich bei strcpy :D

Ah, okay. Ich denke mal die Unix-basierten Systemen verhalten sich da vielleicht ähnlich.

Neomi
2011-02-09, 22:50:29
char * text = new char[strlen("abcd")];
strcpy(text, "abcd");

Kleiner Flüchtigkeitsfehler deinerseits, aber sehr wichtig zu erwähnen, damit der Threadstarter es richtig lernt: nie, nie, NIE die terminierende Null vergessen.

Krishty
2011-02-09, 23:28:40
Kleiner Flüchtigkeitsfehler deinerseits, aber sehr wichtig zu erwähnen, damit der Threadstarter es richtig lernt: nie, nie, NIE die terminierende Null vergessen.Autsch, ja. Ich wollte eigentlich erst sizeof benutzen, aber dann hätte mich die wchar_t-Fraktion in der Luft zerrissen, drum hatte ich es im letzten Moment durch strlen() ersetzt … m[

(Für alle: strlen() gibt die Länge ohne Null zurück; das strcpy() hätte also über die Bereichsgrenze hinausgeschrieben. sizeof gibt die Größe in Bytes zurück, da char garantiert ein Byte groß ist, ist sie identisch mit der Anzahl Buchstaben plus Null. Bei allen anderen Typen muss man sizeof(array) / sizeof(array[0]) rechnen, oder sich ein Template bauen, oder manuell 1 dazuaddieren, oder oder oder.)

Ectoplasma
2011-02-10, 08:18:34
Hast du dafür zufällig einen Paragraphen im Standard parat? Ich bin mir sicher, dass Arrays immer by-reference übergeben werden (finde den entsprechenden Abschnitt aber gerade nicht).


Hast Recht, das ist bei Arrays natürlich Quatsch. Die Schreibweise für typedef und den Typen als const Referenz übergeben zu können, ist aber dennoch gültig.

BigRob
2011-02-10, 09:58:18
@Gast:

zum Verständniss kannst du dich mal mit Zeigerarithmethik auseinandersetzen.

Zum Bsp. hier:

http://www.informatik.uni-leipzig.de/~meiler/C/V12.pdf

Senior Sanchez
2011-02-10, 13:54:06
Ich muss sagen, dass das ein sehr interessanter Thread ist. :)

Ich habe zwar schon häufiger etwas mit C/C++ gemacht (wobei das lange her ist, daher die Unsicherheiten ;) ), aber hier hab ich nicht nur das Wissen aufgefrischt, sondern viele kleine Details gelernt, über die ich mir sonst eventuell keine Gedanken gemacht hätte.

Gast
2011-02-10, 14:22:23
Auf jeden Fall, danke auch nochmal an BigRob für das PDF! :)

Gast
2011-02-12, 09:46:22
Schöner Thread, da häng ich auch gleich mal 2 Fragen dran:

Ich möchte zunächst mal fix 6 Byte in einem Array speichern. Beim ersten cout gibt er mir die Größe des Arrays (=6) auch korrekt aus.

unsigned char test[] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66 };
cout << sizeof(test) << endl;

Kann ich eigentlich mit cout den Inhalt des Arrays wiedergeben, so dass da dann auch wirklich 0x11, 0x22 usw. und nicht das Zeichen des jeweiligen Hex-Codes steht?


Nun lasse ich einen Zeiger auf test[] zeigen:

unsigned char *pTest = test;

Wie kann ich nun über die Zeigervariable an die Länge von test bzw. an den Inhalt zur Augabe über cout gelangen?

Ectoplasma
2011-02-12, 12:13:19
unsigned char test[] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66 };
cout << sizeof(test) << endl;

Kann ich eigentlich mit cout den Inhalt des Arrays wiedergeben, so dass da dann auch wirklich 0x11, 0x22 usw. und nicht das Zeichen des jeweiligen Hex-Codes steht?


Ich glaube nicht, dass man das mit cout so einfach machen kann. Da brauchst du eine eigene Funktion für. Du kannst natürch std::vector benutzen. Dann definierst du dir einen eigenen Typen für deinen Vector und überlädst den << operator für cout.


Nun lasse ich einen Zeiger auf test[] zeigen:

unsigned char *pTest = test;

Wie kann ich nun über die Zeigervariable an die Länge von test bzw. an den Inhalt zur Augabe über cout gelangen?


Solche Konstrukte sind absolut evil. Dein Array hat keinen Null-Terminator. Du könntest mit pTest ja noch andere Dinge anstellen wollen. Jedenfalls zeigt pTest auf einen nicht gültigen String. Abgesehen davon, dass du über pTest die Länge nun nicht mehr ermitteln kannst. C++ und ich glaube auch kaum eine andere Sprache, überträgt Metainformationen an einen L-Value (unsigned char *pTest), welcher zudem von einem anderen Typ (unsigned char test[]) ist. C++ hält soetwas für integrale Typen nicht vor. Der sizeof Implementierung für Pointer wiederspricht es auch. Also die Antwort lautet, das geht so nicht.

Senior Sanchez
2011-02-12, 12:20:19
Ich glaube nicht, dass man das mit cout so einfach machen kann. Da brauchst du eine eigene Funktion für. Du kannst natürch std::vector benutzen. Dann definierst du dir einen eigenen Typen für deinen Vector und überlädst den << operator für cout.

Warum sollte das nicht gehen? Er schiebt doch im Grunde nur ein short rein, dass beschreibt, wie viel Speicher dieses Array umfasst.

Beim Rest gebe ich dir aber Recht.

pest
2011-02-12, 14:55:28
cout << hex << test << endl;
cout.setf(ios::hex,ios::basefield);
cout << test << endl;

Gast
2011-02-12, 15:11:30
Das klappt aber doch nur mit integern?

Krishty
2011-02-12, 15:54:38
Kann ich eigentlich mit cout den Inhalt des Arrays wiedergeben,Ja, aber dazu musst du in einer Schleife über alle Elemente iterieren. Sonst denkt cout nämlich, du wolltest einen Zeiger auf das erste Element des Arrays ausgeben.
so dass da dann auch wirklich 0x11, 0x22 usw. und nicht das Zeichen des jeweiligen Hex-Codes steht?Ja, (wie pest schreibt) mit hex und einem Cast zu einem unsigned int-Typ. (Warum cout unsigned char als Buchstaben ausgibt bzw. unsigned char * als String interpretiert kann ich persönlich nicht nachvollziehen.)
Wie kann ich nun über die Zeigervariable an die Länge von test bzw. an den Inhalt zur Augabe über cout gelangen?Garnicht. Zeiger an sich enthalten außer dem Typ keine Metainformationen über den Speicher, auf den sie Zeigen – und da du in dem Fall einen Zeiger auf das erste Element anlegst, weißt du nur, dass dort vielleicht ein oder mehrere unsigned ints liegen.

Ectoplasma
2011-02-13, 18:39:49
cout << hex << test << endl;
cout.setf(ios::hex,ios::basefield);
cout << test << endl;


Cool, wieder was gelernt. Danke.