Wie sind In/Out Parameter grundsätzlich zu handeln?
-
Hallo zusammen,
ich tue mich echt schwer mit C/C++:)
Ich habe in einer Funktion eine dynamische String Variable, die irgendwo anders im Programm initialisiert werden soll.
Geht das auch eleganter als im folgendem Code?
[cpp] void Haupt() { char szInput[] = "Kopier mich"; char * szOutput; // Wird erst in SubSub initialisiert int iDummy = Sub(szInput, &szOutput); // Sollte klappen, aber ich mich wundert nix mehr:) ASSERT(memcmp(szInput, &szOutput, strlen(szInput))); if (szInput) free(szOutput); } int Sub(const char *szInput, char **szOutput) { *szOutput = SubSub(szInput, strlen(szInput)); return strlen(*szOutput); } char* SubSub(const char *szValue, const DWORD dwLen) { char *szResult = (char *) malloc(dwLen + 1); strncpy(szResult, szValue, strlen(szValue)); szResult[dwLen] = 0; return szResult; } [/cpp]Das "char **szOutput" finde ich so komisch. Ist es das auch?
Was ich absolut noch nicht verstanden habe, ist es, wie mit In/Out Parameter umzugehen ist, wenn der (oder die) Out Parameter nicht vom Caller initialisiert wird. Oder ist es grundsätzlich empfohlen, den Caller initialisieren zu lassen? Aber was, wenn die Speichergröße nicht bekannt ist?
Vor dem Posten habe ich schon eine Weile gegoogelt, konnte aber nix vernünftiges finden. Auch für Links zum Thema Best Practice In/Out Parameter (oder so) wäre ich daher dankbar.
Danke und Grüße
Carsten
-
carsten112 schrieb:
Ich habe in einer Funktion eine dynamische String Variable 1),
die irgendwo anders im Programm initialisiert 2) werden soll.ad 1) In C (Dein Code ist C) gibt es keine "String Variablen". Es gibt entweder
char-Arrays:char text[20];pointer auf char: ("C-Strings")
char* text = "Hello";oder, in C++ strings:
string text = "Text";ad 2)
Aufgrund Deines Folgenden Codes und der Sätzecarsten112 schrieb:
Was ich absolut noch nicht verstanden habe, ist es, wie mit In/Out Parameter umzugehen ist,
wenn der (oder die) Out Parameter nicht vom Caller initialisiert wird. Oder ist es grund-
sätzlich empfohlen, den Caller initialisieren zu lassen? Aber was, wenn die Speichergröße
nicht bekannt ist?gehe ich davon aus daß die Variable "irgendwo anders im Programm" alloziirt, also
Speicherplatz reserviert werden soll!?Dein Code:
void Haupt() { char szInput[] = "Kopier mich"; // // Entschuldige, aber es gibt viele gute Gründe gegen die ungarische Notation // Ich selbst wurde bekehrt ;) Such mal im Forum... char * szOutput; // Wird erst in SubSub initialisiert *) // // im Allgemeinen ist entweder // // [type]* [varname]; oder // [type] *[varname]; üblich. // // *) wie gesagt, wird alloziiert. //=========================================================================== // AB HIER HAB ICH KEINEN DUNST, WAS DU TUST (bzw. tun willst) !!! // // aber vielleicht weißt Du das auch nicht :rolling_eyes: // ich kommentier mal für Dich, was du tust... int iDummy = Sub(szInput, &szOutput); // // Da du iDummy nicht brauchst, brauchst du iDummy auch nicht. // Nur weil eine Funktion etwas zurück gibt, musst du die Rückgabe nicht // unbedingt speichern: // // Sub(szInput, &szOutput); // allein - wenn schon - würd reichen. // +> hier rufst Du Sub() auf, also mal Sub() ansehen... // // so, nun haste in szInput und szOutput das selbe. // strcpy( szOutput, szInput ); würde dasselbe machen. // // // Sollte klappen, aber ich mich wundert nix mehr: ASSERT(memcmp(szInput, &szOutput, strlen(szInput))); // +> nun kopierst du das eben mit Sub() bzw. SubSub() kopierte nochmal // drüber? wieso? // if( szInput ) free( szOutput ); // hm, szInput ist laut Deiner Definition (char szInput[] = "Kopier mich";) // immer true. Aber wie zum Teufel kommst Du darauf, daß desshalb // Speicher für szOutput reserviert worden sein muss? // Was wäre, schlüge das "char *szResult = (char *) malloc(dwLen + 1);" // in SubSub() fehl? } // Hm. Sub tut ja garnichts, außer sofort eine andere Funktion aufzurufen // int Sub(const char *szInput, char **szOutput) { *szOutput = SubSub(szInput, strlen(szInput)); // +> hier rufst Du SubSub() auf, also mal SubSub() ansehn... return strlen(*szOutput); } // Hm. Sub reserviert speicher, kopiert szValue in diesen und // gibt die Adresse zurück. hätte das nicht schon Sub() machen // können? // char* SubSub(const char *szValue, const DWORD dwLen) { char *szResult = (char *) malloc(dwLen + 1); // +> hier solltest Du prüfen, ob malloc() erfolgreich // war. Es könnte auch nicht genügend speicher zur // Verfügung stehen und somit 0 (NULL) zurückgegeben // werden. strncpy(szResult, szValue, strlen(szValue)); szResult[dwLen] = 0; return szResult; }carsten112 schrieb:
Das "char **szOutput" finde ich so komisch. Ist es das auch?
Nö!
ich würd' nen C-String (wenn man die funktionen dazu schon selbst schreiben will/muss)
so kopieren:#include <stdlib.h> #include <stdio.h> // gibt die Länge des übergebenen C-Strings zurück (ohne '\0') size_t strlen( const char* text ) { static size_t length = 0; if( text == 0 ) return 0; if( *text == '\0' ) return length; ++length; return strlen( ++text ); } // reserviert Speicher für *dest und kopiert src nach *dest; // gibt die Länge des kopierten C-Strings zurück (ohne '\0') size_t strcpy( char** dest, const char* src ) { if( src == 0 ) return -1; size_t length = strlen( src ); *dest = (char*) malloc( length + 1 ); if( *dest == 0 ) return -2; for( size_t i = 0; i < length; i++ ) *(*dest + i) = src[i]; return length; } int main( ) { char text[100] = "Hello!"; char *copy = 0; strcpy( ©, text ); printf( "text = \"%s\"\ncopy = \"%s\"\n\n", text, copy ); return 0; }HIH
Greetz, SwordfishBTW: Dieser Thread gehört in's ANSI-C Forum.
-
carsten112 schrieb:
ich tue mich echt schwer mit C/C++:)
Naja, erstmal solltest du dich für eine Sprache entscheiden. Es gibt keine Sprache C/C++. Und beides gleichzeitig zu lernen, macht keinen Sinn.
carsten112 schrieb:
Ich habe in einer Funktion eine dynamische String Variable, die irgendwo anders im Programm initialisiert werden soll.
Nur mal kurz zur Begriffsdefinition. Was du machen willst, geht nicht. Initialisierung ist nur bei der Definition einer Variablen möglich. In diesem Zusammenhang kannst du dir auch mal RAII anschauen. Du kannst aber eine Variable uninitialisert definieren (wie im Beispiel) und später einen Wert _zuweisen_. Wichtig dabei ist nur, dass du erst lesend zugreifst, _nachdem_ die Zuweisung erfolgte.
carsten112 schrieb:
Das "char **szOutput" finde ich so komisch. Ist es das auch?
Nein, nicht wirklich.
carsten112 schrieb:
Was ich absolut noch nicht verstanden habe, ist es, wie mit In/Out Parameter umzugehen ist, wenn der (oder die) Out Parameter nicht vom Caller initialisiert wird. Oder ist es grundsätzlich empfohlen, den Caller initialisieren zu lassen? Aber was, wenn die Speichergröße nicht bekannt ist?
Alles letztendlich eine Frage des persönlichen Geschmacks und der Dokumentation. Wenn der Caller nicht initialisiert, dann muss sich auf jeden Fall der Callee darum kümmern. Logisch, oder? Du musst halt nur dafür sorgen, dass alle benötigten Informationen zur Verfügung stehen und die Funktionsweise vernünftig dokumentiert ist.
Eine Sache würde ich unter C allerdings nie machen. Und zwar eine zusammengehörige Speicherreservierung und -freigabe auf unterschiedlichen Ebenen zu handeln. Dein Callee reserviert Speicher und du zwingst damit den Caller die Freigabe auf. Das ist nicht sehr nett.Kommen wir nochmal konkret zu deinem Beispiel zurück. So wie ich das sehe, besteht dein Problem schlichtweg darin, einen String zu kopieren. Du könntest das natürlich einfach so machen:
char input[] = "Kopier mich"; char output[100]; strcpy(output, input);Das ist aber nicht sonderlich dynamisch und kann zudem Fehler verurschen. Letzteres können wir zwar mit strncpy umgehen, grossartig besser wird es dadurch aber auch nicht. Irgendwann sind dann ein paar "schlaue" Leute auf die Idee gekommen, eine Funktion names strdup zu implementieren. Glücklicherweise gehört diese nicht zum Standard, da sie die bereits angesprochenen Mankos beim Speicherhandling mitbringt. Wie also soll der String kopiert werden? Mein Tipp wäre folgender:
char input[] = "Kopier mich"; char* output = malloc(strlen(input) + 1); if (!output) // Fehlerhandling ; strcpy(output, input); //... free(output);Ist nicht wirklich schön, in C aber imo die sauberste Lösung. Du kannst zwar noch etwas optimieren, indem du das Ergebnis von strlen speicherst und dann mit memcpy kopierst. Das Prinzip bleibt aber gleich.
Seit C99 gibt es allerdings auch VLAs. Damit wird der Code noch etwas kürzer. Allerdings bin ich mir nicht sicher, inwiefern VLAs von Compilern ünterstützt werden. Ich persönlich habe damit noch nicht gearbeitet. Was aber nicht heissen soll, dass VLAs schlecht wären.Und da kommen wir auch schon auf den Anfang meines Beitrages zurück? C oder C++? Wenn du dich für C++ entscheidest, dann wird es denkbar einfach:
char input[] = "Kopier mich"; std::string output = input;btw:
Wie mein Vorredner schon sagte, vergiss UN. Die ist nur hinderlich und hat keine wirklichen Vorteile.
Und strlen gibt size_t zurück und nicht DWORD. DWORD kennt der Standard nicht mal.
Zudem solltest du const nur dort verwenden, wo es sinnvoll ist.char* SubSub(const char *szValue, const DWORD dwLen)Zweimal const, aber einmal vollkommen unnütz. Das erste const ist in Ordnung, da es sich auf den Wert bezieht, auf den der Zeiger verweist. Somit kann er in der Funktion nicht versehentlich geändert werden, was ja Auswirkungen nach aussen hätte. Und du kannst zudem const char* Argumente übergeben und nicht nur char*. Das zweite const ist aber Unfug. Damit verhinderst du lediglich, dass das lokale dwLen geändert wird. Aber wozu? Eine Änderung von dwLen hätte nach aussen sowieso keine Auswirkungen, da der Parameter by-value übergeben wird.
Swordfish schrieb:
// Was wäre, schlüge das "char *szResult = (char *) malloc(dwLen + 1);" // in SubSub() fehl?Naja, szOutput wäre ein Nullzeiger und free würde nichts machen. Aber bis dorthin würde man wohl gar nicht erst kommen, da das Programm wegen einem ungültigen Speicherzugriff vorher schon den Weg ins Nirvana antritt.
-
Hallo zusammen,
zunnächst mal vielen Dank für die ausführlichen Antworten. Wirklich sehr beeindruckend dieses Forum!
Sorry, zu meinem Quelltext hätte ich wohl noch anmerken sollen, das völlig egal ist, was in den Funktionen gemacht; es ging mir nicht um das Kopieren des C-strings, sondern nur um das Alloziieren einer Variable in "SubSub", denn genau das hat bei mir lange nicht hingehauen und klappte erst mit "char **szOutput" und das fand ich halt optisch "komisch".
Was Ihr gegen die UN habt, will mir jetzt überhaupt nicht einleuchten. Verwende sie seit Jahren (in anderen Sprachen) und find's eigentlich recht praktisch. Naja, werde mal im Forum suchen...
if( szInput ) free( szOutput );szInput sollte szOutput sein:)
// Sollte klappen, aber ich mich wundert nix mehr: ASSERT(memcmp(szInput, &szOutput, strlen(szInput))); // +> nun kopierst du das eben mit Sub() bzw. SubSub() kopierte nochmal // drüber? wieso?Nein, mit memcmp vergleiche ich den Speicher um zu prüfen, ob das Kopieren (um das es ja eigentlich gar nicht geht) hingehauen hat.
groovemaster schrieb:
Eine Sache würde ich unter C allerdings nie machen. Und zwar eine zusammengehörige Speicherreservierung und -freigabe auf unterschiedlichen Ebenen zu handeln. Dein Callee reserviert Speicher und du zwingst damit den Caller die Freigabe auf. Das ist nicht sehr nett.
Aber wenn der Caller die benötigte Speichergröße nicht kennt, und der Callee nur einmal aufgerufen werden soll, was denn? Zunnächst den Callee aufrufen um die Speichergröße erfragen, kann doch auch nicht immer Sinn ergeben, denn wenn die Größe erst nach umfangreicher Berechnung feststeht, dann müßte die ja zweimal durchlaufen werden.
groovemaster schrieb:
Naja, erstmal solltest du dich für eine Sprache entscheiden.
Warum eigentlich? Jeder C++ Compiler kann doch auch C!? Auf C++ will ich wegen OO nicht verzichten. Trotzdem sollte es doch kein Problem bzw. Manko sein, auch in C++ z.b. mit C-Strings zu arbeiten!?
Grüße
Carsten
-
carsten112 schrieb:
Sorry, zu meinem Quelltext hätte ich wohl noch anmerken sollen, das völlig egal ist, was in den Funktionen gemacht; es ging mir nicht um das Kopieren des C-strings, sondern nur um das Alloziieren einer Variable in "SubSub", denn genau das hat bei mir lange nicht hingehauen und klappte erst mit "char **szOutput" und das fand ich halt optisch "komisch".
Das liegt daran, daß Variablen in C(++) per Wert übergeben werden - wenn du die Variable des Hauptprogramms verändern willst, mußt du einen Pointer (oder in C++ eine Referenz) darauf verwenden.
(und wenn deine zu verändernde Variable ein char* ist, landest du unweigerlich bei einem Doppelpointer)Was Ihr gegen die UN habt, will mir jetzt überhaupt nicht einleuchten. Verwende sie seit Jahren (in anderen Sprachen) und find's eigentlich recht praktisch. Naja, werde mal im Forum suchen...
Jedem so, wie er mag
Ich äußere mich nicht weiter dazu.Aber wenn der Caller die benötigte Speichergröße nicht kennt, und der Callee nur einmal aufgerufen werden soll, was denn? Zunnächst den Callee aufrufen um die Speichergröße erfragen, kann doch auch nicht immer Sinn ergeben, denn wenn die Größe erst nach umfangreicher Berechnung feststeht, dann müßte die ja zweimal durchlaufen werden.
In dem Fall wird dir nichts anderes übrigbleiben, als das zu trennen.
In C++ hast du immer noch die Möglichkeit, Objekte mit eigener Speicherverwaltung zu übergeben oder einen nackten Pointer in einen Smart-Pointer zu verpacken.Warum eigentlich? Jeder C++ Compiler kann doch auch C!? Auf C++ will ich wegen OO nicht verzichten. Trotzdem sollte es doch kein Problem bzw. Manko sein, auch in C++ z.b. mit C-Strings zu arbeiten!?
Wenn du die OO nutzen willst, dann mach das, aber bitte konsequent

-
Wenn du du wuesstes was OO ist, und wie man damit arbeiten kann.
Ich behaubte mal, wenn du C++ wirklich kennen wuerdest, was du nicht
tust, wuerdest du C nur noch fuer API einbindungen gebrauchen.Du hast keine ahnung was C++ strings sind. Du hast null ahnung was c++
ist. wieso? lies mal dein Text hier.Gg
-
@CStoll
DankexGhost schrieb:
Wenn du du wuesstes was OO ist, und wie man damit arbeiten kann.
So, so, mein Code, der rein gar nix mit OO zu tun hat, zeigt dir also, ob ich Ahnung von OO habe. Lächerlich.
-
carsten112 schrieb:
es ging mir nicht um das Kopieren des C-strings, sondern nur um das Alloziieren einer Variable in "SubSub", denn genau das hat bei mir lange nicht hingehauen und klappte erst mit "char **szOutput" und das fand ich halt optisch "komisch".
Du kannst nunmal Zeiger auf Zeiger (auf Zeiger auf Zeiger auf Zeiger ...) Variablen haben. Das mag zwar komisch aussehen, ist aber nichts Ungewöhnliches. Kannst ja zur Not ein typedef machen:
typedef char* c_string; typedef const char* const_c_string; int Sub(const_c_string input, c_string* output)Dann sieht's schon wieder "normal" aus.

carsten112 schrieb:
Was Ihr gegen die UN habt, will mir jetzt überhaupt nicht einleuchten. Verwende sie seit Jahren (in anderen Sprachen) und find's eigentlich recht praktisch. Naja, werde mal im Forum suchen...
Nun, ich habe früher auch mal UN verwendet. Das hatte sich zwangsläufig so ergeben, als ich bei WinAPI landete und man es halt von diversen Quelltexten kannte. Damals dachte ich auch es sei toll und praktisch. Irgendwann habe ich es einfach mal weggelassen ... und mir fehlt bis heute nichts (naja, zumindest nicht in diesem Punkt
). Der Quelltext lässt sich einfacher lesen und du brauchst dir auch nicht jedesmal bei einer Variablendeklaration Gedanken darüber machen, wie sie laut UN heissen muss. Das ist, zumindest für mich, eine richtige Erleichterung gewesen. Aber wenn du unbedingt UN verwenden willst, dann will ich dich auch nicht davon abbringen. Aber zumindest darüber nachdenken solltest du. In dem Zusammenhang kann ich dir auch diesen Artikel auf Wiki empfehlen, besonders den Abschnitt über Vor- und Nachteile. Du wirst sehen, mit aktuellen IDEs gibt es praktisch keine Vorteile. Oder benutze die Forensuche, da gibt es reichlich Diskussionstoff.carsten112 schrieb:
Nein, mit memcmp vergleiche ich den Speicher um zu prüfen, ob das Kopieren (um das es ja eigentlich gar nicht geht) hingehauen hat.
Das brauchst du nicht. memcpy oder strcpy funktionieren _immer_. Es ist simples Speicherkopieren, ohne jegliche Fehlerzustände. Es gibt nur zwei Dinge, die passieren können. Erstens, die zu kopierenden Speicherbereiche können sich überlappen. Dafür gibts memmove. Und zweitens, deine Hardware ist defekt. Dann kannst du dich auf memcmp aber auch nicht mehr verlassen.
carsten112 schrieb:
Aber wenn der Caller die benötigte Speichergröße nicht kennt, und der Callee nur einmal aufgerufen werden soll, was denn? Zunnächst den Callee aufrufen um die Speichergröße erfragen, kann doch auch nicht immer Sinn ergeben, denn wenn die Größe erst nach umfangreicher Berechnung feststeht, dann müßte die ja zweimal durchlaufen werden.
Ich würde die Informationsbeschaffung für den Callee und das, was der Callee letztendlich machen soll, grundsätzlich trennen. Dann musst du zwar zwei Funktionen aufrufen, aber nichts ist redundant. Da fällt mir spontan ein Beispiel aus der WinAPI ein:
GetWindowText
Mit der Funktion kannst du den Text eines Steuerelements (oder Fensters) ermitteln. Dazu übergibst du einen Zeiger auf ein Zeichen-Array, wo der Text gespeichert wird. Du weisst natürlich vorher nicht, wie lang das Array sein muss. Du kannst der Funktion zwar eine Maximallänge mitgeben, nur wird der String dann uU abgeschnitten, was natürlich nicht schön ist. Um die benötigte Länge zu ermitteln, bedienst du dich einer zweiten Funktion
GetWindowTextLength.
Und wie CStoll schon sagte, mit C++ hast du die Möglichkeit Smart-Pointer zu benutzen. Dann kannst du den Callee ruhig die Speicherreservierung überlassen und gibst halt einen Smart-Pointer zurück. Der kümmert sich dann selbst um die Speicherfreigabe. Allerdings sollte man vorher gut überlegen, ob sich der Einsatz eines Smart-Pointers wirklich lohnt. Zumal es einige Sachen dabei zu beachten gibt. Also niemals mit Smart-Pointern wild um sich schlagen und bedacht einsetzen!carsten112 schrieb:
Warum eigentlich? Jeder C++ Compiler kann doch auch C!?
Nein. C und C++ sind zwei vollkommen unabhängige Sprachen. C++ ist nicht 100% kompatibel zu C, und C zu C++ schon gar nicht. Es gibt zwar Combo-Compiler, die Quelltexte beider Sprachen verarbeiten können (zB der MSC), du solltest das trotzdem immer sauber trennen.
carsten112 schrieb:
Auf C++ will ich wegen OO nicht verzichten.
Dann nimm C++ und vergiss C. Du kannst mit C++ alles machen, was du mit C machen kannst. Umgekehrt ist das nicht so ohne weiteres möglich. Es gibt Leute, die denken C++ == OO == Klassen (siehe xGhost). Das ist aber Unsinn. Sicherlich wurde C++ irgendwann mal, mit der Vorgabe eine OO Sprache zu haben, entwickelt. Herausgekommen ist aber eine Multi-Paradigmen Sprache. Dh, du kannst mit C++ genauso wie mit C arbeiten. Schliesslich ist die Sprache eine rein semantische und syntaktische Definition und kein Konzept. Auch wenn sich die Frage stellt, warum jemand dann überhaupt C++ verwendet. Das ist aber ein anderes Kapitel.
carsten112 schrieb:
Trotzdem sollte es doch kein Problem bzw. Manko sein, auch in C++ z.b. mit C-Strings zu arbeiten!?
Nein, natürlich nicht. Aber C-Strings selbst sind schon ein Manko. Also wenn du nicht unbedingt darauf angewiesen bist (zB durch die WinAPI), dann verwende sie auch nicht.
-
groovemaster schrieb:
Dann nimm C++ und vergiss C. Du kannst mit C++ alles machen, was du mit C machen kannst. Umgekehrt ist das nicht so ohne weiteres möglich. Es gibt Leute, die denken C++ == OO == Klassen (siehe xGhost). Das ist aber Unsinn.
Da gebe ich dir 100% recht. Hmm irgendwie gibt mein text
ein falsches bild. Ich wollte auch nicht carsten112 in
irgend eine weise beleidigen.ich wollte eigentlich klar machen, das cstrings in
keiner weise vorteile gegenueber string(c++) haben.
Es gibt ja auch noch die string().c_str() funktion.Gg