Hallo Besucher, der Thread wurde 13k mal aufgerufen und enthält 93 Antworten

letzter Beitrag von Zirias/Excess am

C Frage ist folgendes zulässig?

  • Es sei der Einfachheit halber folgende structure gegeben:



    struct test { char *name; } structure, *pointer;



    ferner



    pointer = (struct test *) malloc(sizeof(struct test));



    Dann geht es folgendermaßen weiter:



    structure.name=(char *) malloc (sizeof("Emily"+1));



    strcpy (structure.name,"Emily");


    ..


    Folgendes Problem: im ersten malloc wird eine Anzahl von Bytes (bei mir sind es 4) für die struct test reserviert.


    zu diesem Zeitpunkt steht aber noch nicht fest, wie lange der String structure.name wird.


    Die Reservierung von Speicherplatz für "Emily", das sind noch einmal 6 Bytes, also mehr Bytes als die Struktur insgesamt zugewiesen bekommt (Bei mir eben 4 Bytes).


    Insofern sprengt das 2. malloc den Speicherplatz für die Struktur insgesamt (1. malloc).


    Daher frage ich mich, ob das überhaupt so zulässig ist.

  • Doch, das ist fehlerfrei. Nur musst du bedeneken, dass in den allozerten Bereich structure.name nur ein Pointer auf einen weiteren zu allozerenden String ist.

  • Die Reservierung von Speicherplatz für "Emily", das sind noch einmal 6 Bytes, also mehr Bytes als die Struktur insgesamt zugewiesen bekommt (Bei mir eben 4 Bytes).


    Insofern sprengt das 2. malloc den Speicherplatz für die Struktur insgesamt (1. malloc).

    Die Speicheranforderung für den String hat nichts mit dem Speicherplatz für die struct zu tun - der String steht ja gar nicht in der struct drin.

  • Soweit ich es überblicke, reserviert das erste "malloc" den Speicher für ein "struct test", und das sind je nach Plattform 2, 4, oder 8 bytes (wie lange halt ein Pointer jeweils ist). Die erhaltene Addresse wird in der Variable "pointer" festgehalten, die tatsächlich auch als Pointer auf ein "struct test" deklariert ist.


    Das zweite malloc reserviert Speicher für einen String "Emily" (nicht für ein zweites struct test! das ist mit dem Namen "structure" schon vorhanden!), und schreibt die so erhaltene Addresse in das bereits definierte "structure.name". Durch die Deklaration (ohne *) wurde vorher der Speicher für "structure" bereits reserviert, und die Verwendung greift direkt auf das struct zu.


    Was wir jetzt haben, sind zwei "struct test", wobei das eine direkt auf dem Stack definiert ist (Deklaration von "structure"), das zweite aber auf dem Heap (über malloc, wobei "pointer" die Addresse enthält).


    Dann wird mit der strcpy-Funktion dem vorher grad reservierten String-Speicherbereich, dessen Adresse in "struct.name" steht, byte für byte der Inhalt der String-Konstante "Emily" zugewiesen (er ist ja dank malloc groß genug), während der "pointer" gar nicht mehr benutzt wird (zumindest in dem hier sichtbaren Teil des Beispiels).

  • Genau - wie ich schon geschrieben habe:

    Nur musst du bedeneken, dass in den allozerten Bereich structure.name nur ein Pointer auf einen weiteren zu allozerenden String ist.

  • Sorry, ist ein alter Thread, aber muss hier trotzdem mal meinen Senf geben:

    struct test { char *name; } structure, *pointer;

    Das ist soweit zwar korrekt, aber schwer/schlecht lesbar. Mehrere Deklarationen auf einmal sollte man für sauberen Stil besser vermeiden, solche Praktiken stammen noch aus den Zeiten, als man auf 80x25 text screens programmiert hat :) Für den resultierenden Code ist es egal, und folgendes ist besser lesbar:


    Code
    1. struct test {
    2. char *name;
    3. };
    4. struct test structure;
    5. struct test *pointer;


    Oder man nimmt ein typedef dazu um dem Typen einen Namen im normalen Namespace zu geben, dann kann man sich das `struct` später sparen. Auch die Variablenbenennung ist "fragwürding", ein pointer ist wohl sehr allgemein.


    pointer = (struct test *) malloc(sizeof(struct test));

    Das ist auch nicht falsch, aber unnötig kompliziert und auch eine klassische Fehlerquelle. Erstens ist in C (nicht in C++!) die Konvertierung eines Pointer-Typs von oder zu void * implizit, der Cast ist also überflüssig. void * fungiert hier als "generischer" Typ. Da Pointer-Casts in C potentiell gefährlich sind (der resultierende Code könnte die "strict aliasing rule" verletzen) sollte man Casts, die nicht nötig sind, auch nicht schreiben -- und dann alle anderen als Alarmsignal sehen und den Code genau überprüfen. Zweitens kennt der malloc operator zwei verschiedene Formen: Mit Klammern und einem Typnamen, oder ohne klammern mit einer Expression. Der bevorzugte C-Stil für diese Zeile sieht so aus:

    Code
    1. pointer = malloc(sizeof *pointer);

    In dieser Form bezieht sich sizeof auf die Storage-Größe des Typs der Expression *pointer, das ist genau der gewünschte Typ struct test. Der Vorteil dieser Schreibweise, neben der besseren Lesbarkeit, ist, dass sie auch noch korrekt ist, wenn man später den Typ von pointer ändert. Mit der Syntax weiter oben müsste man dann auch den Operanden zu sizeof ändern -> leicht zu übersehen, damit eine Quelle für Bugs.



    structure.name=(char *) malloc (sizeof("Emily"+1));

    Diese Zeile ist völlig falsch!. Ein String literal in C hat den Typ char[], damit würde das passen, aber durch das +1 wird das Array Teil einer Expression, in der es zu einem Pointer evaluiert. "Emily"+1 ist ein Pointer (Typ char *) auf das m, damit liefert dieses sizeof die Größe eines Pointers -- höchstwahrscheinlich weniger als 6.


    Gemeint war wohl sizeof("Emily")+1, das wäre korrekt, würde aber ein Byte verschwenden -- ein String literal enthält bereits das abschließende NUL Byte, sonst wäre es im Sinn von C gar kein String.


    Bevorzugte Schreibweise:

    Code
    1. structure.name = malloc(sizeof "Emily");

    Die Klammern zu sizeof sind überflüssig, weil wieder eine Expression verwendet wird und nicht der name eines Typs (das wäre hier dann char[6]).


    Die eigentliche Frage wurde ja schon beantwortet, aber diese Anmerkungen finde ich durchaus noch wichtig :)

  • Sorry, ist ein alter Thread, aber muss hier trotzdem mal meinen Senf geben:

    Das ist soweit zwar korrekt, aber schwer/schlecht lesbar. Mehrere Deklarationen auf einmal sollte man für sauberen Stil besser vermeiden, solche Praktiken stammen noch aus den Zeiten, als man auf 80x25 text screens programmiert hat :) Für den resultierenden Code ist es egal, und folgendes ist besser lesbar:

    Code
    1. struct test {
    2. char *name;
    3. };
    4. struct test structure;
    5. struct test *pointer;


    Oder man nimmt ein typedef dazu um dem Typen einen Namen im normalen Namespace zu geben, dann kann man sich das `struct` später sparen. Auch die Variablenbenennung ist "fragwürding", ein pointer ist wohl sehr allgemein.


    Sehe ich alles genauso... bis auf die Deklaration des structs. Da bevorzuge ich diese (auch von Stroustrup bevorzugte) Schreibweise,
    die für mich besser lesbar erscheint:


    C
    1. struct test
    2. {
    3. char* name;
    4. };
    5. struct test structure;
    6. struct test* pointer;


    So... und nun kan der "Holy War" losgehen! :bgdev

  • Das ist ein "C++-ism" (genau wie der cast von void *) :)


    Man kann natürlich in jeder Sprache so schreiben, wie man will -- die übliche Philosophie in C ist allerdings, dass eine Deklaration möglichst genau so aussehen soll, wie die Verwendung. Der * wird dabei als eine Art "type modifier" gesehen und bindet entsprechend auch an den Identifier, nicht an den Typ. In C deklariert man also quasi den Typ des dereferenzierten Pointers, daher die vorgeschlagene Schreibweise oben.


    In C++ ist die Philosophie, Typ und Identifier klar voneinander zu trennen -- das erscheint zunächst logischer und ist in C++ auch die übliche Notation. Leider wird es in C++ unlogisch, wenn man sowas hat: char* x, y; -- die Deklaratoren sind hier kompatibel zu C, also deklariert das nicht zwei Pointer sondern nur einen plus ein char. Wenn man natürlich der Empfehlung folgt, nicht mehrere Dinge auf einmal zu deklarieren, spielt es keine Rolle, aber es ist ein wenig unschön :)

  • ..... Wenn man natürlich der Empfehlung folgt, nicht mehrere Dinge auf einmal zu deklarieren, spielt es keine Rolle, aber es ist ein wenig unschön :)


    Randnotiz: In den meisten Projekten (an denen ich mitarbeiten durfte) war es sogar explizit verboten worden, mehrere Variablen in einer Zeile zu deklarieren.

  • Randnotiz: In den meisten Projekten (an denen ich mitarbeiten durfte) war es sogar explizit verboten worden, mehrere Variablen in einer Zeile zu deklarieren.

    Ja, das ist ja auch sinnvoll, würde ich in jeder coding guideline so unterstützen :) Ich wollte nur darauf hinweisen, dass Stroustrup hier etwas inkonsequent ist: Er wollte C++ möglichst C-kompatibel halten, deshalb bindet auch in C++ der * in einer Deklaration an den Identifier und nicht an den Typ (obwohl er zum Typnamen gehört), propagiert aber trotzdem eine Schreibweise, die suggeriert es sei umgekehrt.


    Persönlich bleibe ich beim üblichen C-Stil wenn ich C schreibe, und in den seltenen Fällen, dass ich C++ mal anrühre (bin kein großer Fan dieser Sprache) versuche ich daran zu denken, dort den in C++ bevorzugten Stil zu schreiben :)

  • Ja, das ist ja auch sinnvoll, würde ich in jeder coding guideline so unterstützen :) Ich wollte nur darauf hinweisen, dass Stroustrup hier etwas inkonsequent ist: Er wollte C++ möglichst C-kompatibel halten, deshalb bindet auch in C++ der * in einer Deklaration an den Identifier und nicht an den Typ (obwohl er zum Typnamen gehört), propagiert aber trotzdem eine Schreibweise, die suggeriert es sei umgekehrt.
    Persönlich bleibe ich beim üblichen C-Stil wenn ich C schreibe, und in den seltenen Fällen, dass ich C++ mal anrühre (bin kein großer Fan dieser Sprache) versuche ich daran zu denken, dort den in C++ bevorzugten Stil zu schreiben :)


    Und wie gehst du bei Projekten vor, die in C++ programiert sind, aber extern nur ein C API anbieten?




    Zitat

    .... dass Stroustrup hier etwas inkonsequent....


    Manchmal ist Inkonsequenz eine Folge der Weiterentwicklung/Verbesserung!

  • Und wie gehst du bei Projekten vor, die intern in C++ aber extern (also am API) in C programmiert sind?

    Sowas habe ich bisher nie gemacht. Nur mal ein C Projekt, in dem ein Teil in C++ war (wegen Nutzung von Qt) und dieser Teil eine interne C API hatte (ist bisher sowieso unvollendet geblieben) -- da hab ich komplett den typischen C Stil verwendet :)


    Man könnte sich überlegen, dort auch in den .cpp Files den "Stroustrup-Stil" zu nehmen.

  • Sowas habe ich bisher nie gemacht. Nur mal ein C Projekt, in dem ein Teil in C++ war (wegen Nutzung von Qt) und dieser Teil eine interne C API hatte -- da hab ich komplett den typischen C Stil verwendet :)


    Ich habe sehr oft (meistens) solche Projekte, in denen externe libraries "unter der Haube" in C++ programmiert sind, aber das API nur in C abgebildet wird.


    Daher bleibe ich grundsätzlich beim C++ Stil.

  • Daher bleibe ich grundsätzlich beim C++ Stil.

    Hatte ich ja auch gleich geschrieben: Jeder kann es schreiben, wie er es besser findet (solange man sich nicht an existierende coding guidelines halten muss).


    Ich würde aber empfehlen, sich an den in der jeweiligen Sprache bevorzugten (bzw verbreitetsten) Stil zu halten. Projekte, in denen verschiedene Sprachen gemischt sind, sind dann natürlich ein Grenzfall :)


    Übrigens, ein weiteres Argument für die klassische C Schreibweise ist auch, dass es konsistent mit der Deklaration von Arrays ist. Also z.B. bei der Deklaration int x[5]; ist der Typname int[5], ich kann aber trotzdem nicht int[5] x; schreiben...

  • Erstens ist in C (nicht in C++!) die Konvertierung eines Pointer-Typs von oder zu void * implizit, der Cast ist also überflüssig. void * fungiert hier als "generischer" Typ. Da Pointer-Casts in C potentiell gefährlich sind (der resultierende Code könnte die "strict aliasing rule" verletzen) sollte man Casts, die nicht nötig sind, auch nicht schreiben -- und dann alle anderen als Alarmsignal sehen und den Code genau überprüfen.


    Was kann denn da Schlimmes passieren?

  • Was kann denn da Schlimmes passieren?

    §6.5 p7, auch "strict aliasing rule" genannt. Kurz gesagt verbietet sie mit wenigen Ausnahmen, ein Objekt mit einem Pointer eines anderen Typs zu lesen oder zu schreiben.


    Ein Compiler darf beim optimieren natürlich annehmen, dass der Code korrekt ist, also unter anderem auch die "strict aliasing rule" befolgt. Das kann dann zu solchen lustigen Sachen führen. Das ist natürlich ein sinnloses konstruiertes Beispiel, aber man sieht sehr schön im Assembler-Code, dass diese Funktion grundsätzlich 42 ausgibt (Zeile 8 im asm) obwohl die beiden Zuweisungen (Zeilen 6/7 im C code) sequenced sein müssten -- der Compiler darf aber annehmen, dass Zeile 7 den Wert von *intval nicht verändert

    Deshalb ist in C ein pointer-cast immer eine Stelle, die man genau überprüfen muss, ob das so auch valide ist -- ein "red flag" im Code. Also argumentiere ich, unnötige pointer-casts besser wegzulassen. In C++ ist es ein bisschen anders, hier ist es immer erlaubt, auf einen Pointer einer Basisklasse zu casten, die umgekehrte Richtung ist ebenfalls erlaubt wenn das Objekt tatsächlich den konkreteren Typ hat. Der Code oben wäre natürlich auch in C++ "Murks" ;)

  • Das ist ein "C++-ism" ... die übliche Philosophie in C ist allerdings, dass eine Deklaration möglichst genau so aussehen soll, wie die Verwendung. Der * wird dabei als eine Art "type modifier" gesehen und bindet entsprechend auch an den Identifier, nicht an den Typ. In C deklariert man also quasi den Typ des dereferenzierten Pointers, daher die vorgeschlagene Schreibweise oben.


    Weil ich gerade ein paar Bücher durchgehe und schon alle drei Varianten gesehen habe: Wie würdest du folgendes in C deklarieren?


    Code
    1. char *const name


    Code
    1. char * const name


    Code
    1. char* const name