Kann GCC mich vor dem Ändern der Felder einer Konstruktstruktur in C99 warnen?

Lesezeit: 8 Minuten

Benutzeravatar von Samuel Navarro Lou
Samuel Navarro Lou

Ich bin auf ein kleines Problem gestoßen, als ich versuchte, konstant korrekten Code zu erstellen.

Ich hätte gerne eine Funktion geschrieben, die einen Zeiger auf eine const-Struktur nimmt, um dem Compiler zu sagen: “Bitte sagen Sie mir, ob ich die Struktur ändere, weil ich das wirklich nicht will”.

Plötzlich kam mir in den Sinn, dass der Compiler mir dies erlauben würde:

struct A
{
    char *ptrChar;
};

void f(const struct A *ptrA)
{
    ptrA->ptrChar[0] = 'A'; // NOT DESIRED!!
}

Was verständlich ist, denn was eigentlich const ist, ist der Zeiger selbst, aber nicht der Typ, auf den er zeigt. Ich möchte jedoch, dass der Compiler mir sagt, dass ich etwas mache, was ich nicht tun möchte, wenn das überhaupt möglich ist.

Als Compiler habe ich gcc verwendet. Obwohl ich weiß, dass der obige Code legal sein sollte, habe ich trotzdem überprüft, ob er sowieso eine Warnung ausgeben würde, aber es kam nichts. Meine Befehlszeile war:

gcc -std=c99 -Wall -Wextra -pedantic test.c

Ist es möglich, dieses Problem zu umgehen?

  • Dieser Fall arbeitet an der Zuordnung des Mitglieds. Z.B ptrA->ptrChar = malloc(2); Welches Mitglied zeigt, ist nicht so.

    – BLUEPIXY

    26. Februar 2016 um 8:11 Uhr


  • Ähnliche Frage

    – MM

    26. Februar 2016 um 8:52 Uhr

  • Zu diesem Zweck habe ich normalerweise einen Header, der nur forward deklariert struct a;und einige Funktionsdeklarationen wie in a_do_something(const struct *a); Einschließlich der gesamten Definition von struct A in einer Header-Datei ist selten notwendig. Sie setzen viele Funktionen wie Ihre f in getrennten Übersetzungseinheiten, wo f würde ptrA nur irgendwie so verwenden stuff = a_do_something(ptrA);

    – Gabor Buella

    6. März 2016 um 13:26 Uhr


Benutzeravatar von Lundin
Lundin

Eine Möglichkeit, dies bei Bedarf zu umgehen, besteht darin, zwei verschiedene Typen für dasselbe Objekt zu verwenden: einen Lese-/Schreibtyp und einen Nur-Lese-Typ.

typedef struct
{
  char *ptrChar;
} A_rw;

typedef struct
{
  const char* ptrChar;
} A_ro;


typedef union
{
  A_rw rw;
  A_ro ro;  
} A;

Wenn eine Funktion das Objekt ändern muss, nimmt sie den Schreib-Lese-Typ als Parameter, andernfalls den Nur-Lese-Typ.

void modify (A_rw* a)
{
  a->ptrChar[0] = 'A';
}

void print (const A_ro* a)
{
  puts(a->ptrChar);
}

Um die Anruferschnittstelle aufzuhübschen und konsistent zu machen, können Sie Wrapper-Funktionen als öffentliche Schnittstelle zu Ihrem ADT verwenden:

inline void A_modify (A* a)
{
  modify(&a->rw);
}

inline void A_print (const A* a)
{
  print(&a->ro);
}

Mit dieser Methode A kann nun als undurchsichtiger Typ implementiert werden, um die Implementierung für den Aufrufer zu verbergen.

  • Ich mag die Gewerkschaft und bin mir ziemlich sicher, dass sie in der Praxis funktionieren wird; ist es erlaubt? Die Strukturtypen ar, iiuc, inkompatibel.

    – Peter – Setzen Sie Monica wieder ein

    26. Februar 2016 um 9:00 Uhr


  • @Lundin: Vorsicht gcc. Es ist ziemlich aggressiv, wenn es um die Optimierung auf der Grundlage von Annahmen geht, die es aus der strengen Aliasing-Regel ableiten kann.

    – Martin Bonner unterstützt Monika

    26. Februar 2016 um 10:21 Uhr

  • Ja. Ich glaube, Du hast recht. Was mich beunruhigte, war das “es sollte in der Praxis funktionieren” – es sei denn, der Standard sagt, dass es muss, ist es sehr wahrscheinlich (gelegentlich).

    – Martin Bonner unterstützt Monika

    26. Februar 2016 um 14:09 Uhr

  • @MartinBonner Was ich mit diesem Kommentar meine, ist, dass Sie dem Compiler zuerst zwei Strukturen innerhalb einer Union geben, die jeweils einen kompatiblen Zeigertyp enthalten. Es gibt einfach keine Möglichkeit, jede Struktur anders zuzuweisen, warum sollte es? Jede Struktur Wille haben das gleiche Speicherlayout. Und wenn Sie eine Adresse in einer der Strukturen speichern, wird sie unabhängig vom const-Qualifizierer genau gleich gespeichert. Der gesunde Menschenverstand schreibt vor, dass der Datentyp so funktionieren muss und keine andere Implementierung sinnvoll ist. An diesem Punkt wird der Standard irrelevant, weil nichts, was er sagt, die Realität ändern kann.

    – Ludin

    26. Februar 2016 um 14:29 Uhr


  • Sie sagen: “Wenn eine Funktion das Objekt ändern muss, nimmt sie den schreibgeschützten Typ als Parameter, andernfalls den Lese-Schreib-Typ”. Ich gehe davon aus, dass dies ein Tippfehler ist und diese Rollen vertauscht sind.

    – Chris Hayes

    26. Februar 2016 um 18:56 Uhr

Peter - Stellen Sie Monicas Benutzeravatar wieder her
Peter – Wiedereinsetzung von Monica

Dies ist ein Beispiel für Implementierung vs. Schnittstelle oder “Information Hiding” — oder eher Nicht-Verbergen 😉 — Problem. In C++ würde man einfach den Zeiger privat haben und geeignete öffentliche const-Accessoren definieren. Oder man würde eine abstrakte Klasse – eine „Schnittstelle“ – mit dem Accessor definieren. Die eigentliche Struktur würde das implementieren. Benutzer, die keine Struct-Instanzen erstellen müssen, müssten nur die Schnittstelle sehen.

In C könnte man das emulieren, indem man eine Funktion definiert, die einen Zeiger auf die Struktur als Parameter nimmt und einen Zeiger auf const char zurückgibt. Für Benutzer, die keine Instanzen dieser Strukturen erstellen, könnte man sogar einen “Benutzerheader” bereitstellen, der die Implementierung der Struktur nicht durchsickern lässt, sondern nur manipulierende Funktionen definiert, die Zeiger nehmen (oder wie eine Fabrik zurückgeben). Dadurch bleibt die Struktur ein unvollständiger Typ (so dass nur Zeiger auf Instanzen verwendet werden können). Dieses Muster emuliert effektiv, was C++ hinter den Kulissen mit dem tut this Zeiger.

Benutzeravatar von AnArrayOfFunctions
EinArrayOfFunctions

Wenn Sie sich für C11 entscheiden, können Sie vielleicht ein generisches Makro implementieren, das entweder auf die konstante oder die variable Version desselben Elements verweist (Sie sollten auch eine Union in Ihre Struktur aufnehmen). Etwas wie das:

struct A
{
    union {
        char *m_ptrChar;

        const char *m_cptrChar;
    } ;
};

#define ptrChar_m(a) _Generic(a, struct A *: a->m_ptrChar,        \
                                 const struct A *: a->m_cptrChar)//,  \
                                 //struct A: a.m_ptrChar,        \
                                 //const struct A: a.m_cptrChar)

void f(const struct A *ptrA)
{
    ptrChar_m(ptrA) = 'A'; // NOT DESIRED!!
}

Die Gewerkschaft erstellt 2 Interpretationen für ein einzelnes Mitglied. Das m_cptrChar ist ein Zeiger auf die Konstante char und die m_ptrChar bis nicht konstant. Dann entscheidet das Makro je nach Art seines Parameters, worauf es verweisen soll.

Das einzige Problem ist, dass das Makro ptrChar_m kann nur mit einem Zeiger oder Objekt dieser Struktur arbeiten und nicht mit beiden.

  • Schöne Lösung, allerdings muss man darauf verzichten . Fällen, oder Sie bekommen Probleme mit der Makroerweiterung. Vermeiden Sie auch Variablennamen, die mit Unterstrichen beginnen (siehe 7.1.3).

    – Ludin

    26. Februar 2016 um 8:50 Uhr


  • Bitte tun Sie dies nicht. Das ist schrecklich. Niemand wird das in einem Jahr verstehen, nicht einmal du.

    – fuz

    26. Februar 2016 um 10:08 Uhr

  • Ich mag das gewerkschaftliche Wortspiel nicht. Könnten wir stattdessen verwenden _Generic(a, struct A*: a->ptrChar, const struct A*: (const char *) a->ptrChar ) ? Auf diese Weise müssen wir die Definition der Struktur nicht ändern.

    – chi

    26. Februar 2016 um 10:24 Uhr


  • @FUZxxl Das ist der Punkt. Komplexer Arbeitscode ist mein Favorit. Obwohl diese Lösung vielleicht wirklich nicht die beste ist (meine persönliche Meinung).

    – EinArrayOfFunctions

    26. Februar 2016 um 17:42 Uhr

Dies ist ein bekanntes Problem der C-Sprache und nicht vermeidbar. Schließlich ändern Sie nicht die Struktur, sondern ein separates Objekt durch ein Non const-Qualifizierter Zeiger, den Sie aus der Struktur erhalten haben. const Semantik wurde ursprünglich für die Notwendigkeit entwickelt, Speicherbereiche als konstant zu markieren, die physisch nicht beschreibbar sind, und nicht für Bedenken hinsichtlich einer defensiven Programmierung.

Wir könnten die Informationen hinter einigen “Accessor”-Funktionen verstecken:

// header
struct A;           // incomplete type
char *getPtr(struct A *);
const char *getCPtr(const struct A *);

// implementation
struct A { char *ptr };
char *getPtr(struct A *a) { return a->ptr; }
const char *getCPtr(const struct A *a) { return a->ptr; }

Nein, es sei denn, Sie ändern die Strukturdefinition in:

struct A
{
    const char *ptrChar;
};

Eine weitere komplizierte Lösung, die die alte Strukturdefinition intakt hält, besteht darin, eine neue Struktur mit identischen Mitgliedern zu definieren, deren relevante Zeigermitglieder auf Folgendes gesetzt sind: zeigt auf konstanten Typ. Dann wird die aufgerufene Funktion so geändert, dass sie die neue Struktur annimmt. Es wird eine Wrapper-Funktion definiert, die die alte Struktur nimmt, Mitglied für Mitglied in die neue Struktur kopiert und sie an die Funktion übergibt.

Kann GCC mich vor dem Ändern der Felder einer Konstruktstruktur in C99 warnen?

Sie ändern nicht die Felder einer Konstruktstruktur.

Ein Wert von struct A enthält einen Zeiger auf ein nicht konstantes Zeichen. ptrA ist ein Zeiger auf eine Konstruktstruktur A. Sie können also den Wert von Struktur A bei *ptrA nicht ändern. Sie können also den Zeiger bei (*ptrA).Char alias ptrA->ptrChar nicht auf char ändern. Aber Sie ändern den Wert dort, wo ptrA->ptrChar zeigt, dh den Wert bei *(ptrA->Char) alias ptrA->Char[0]. Die einzigen Konstanten hier sind struct As und Sie ändern keine struct A, also was genau ist “nicht erwünscht”?

Wenn Sie keine Änderung des Werts zulassen möchten, auf den das Char-Feld einer Struktur A zeigt (über diese Struktur A), dann verwenden Sie

struct A
{
    const char *ptrChar; // or char const *ptrChar;
};

Aber vielleicht was Sie denken Sie tun in f ist so etwas wie

void f(const struct A *ptrA)
{
    const char c="A";
    ptrA->ptrChar = &c;
}

Das wird einen Fehler vom Compiler erhalten.

1390890cookie-checkKann GCC mich vor dem Ändern der Felder einer Konstruktstruktur in C99 warnen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy