idiomatisches C für konstante Doppelzeiger

Lesezeit: 6 Minuten

Benutzer-Avatar
jwd

Mir ist bewusst, dass Sie in C beispielsweise nicht implizit konvertieren können char** zu const char** (vgl C-FAQSO Frage 1, SO Frage 2).

Auf der anderen Seite, wenn ich eine Funktion sehe, die so deklariert ist:

void foo(char** ppData);

Ich muss davon ausgehen, dass die Funktion die übergebenen Daten ändern kann. Wenn ich also eine Funktion schreibe, wird dies der Fall sein nicht Ändern Sie die Daten, ist es meiner Meinung nach besser zu erklären:

void foo(const char** ppData);

oder auch:

void foo(const char * const * ppData);

Doch das bringt die Nutzer der Funktion in eine missliche Lage. Vielleicht haben Sie:

int main(int argc, char** argv)
{
    foo(argv); // Oh no, compiler error (or warning)
    ...
}

Und um meine Funktion sauber aufzurufen, müssten sie eine Umwandlung einfügen.

Ich habe hauptsächlich einen C++-Hintergrund, wo dies aufgrund der ausführlicheren const-Regeln von C++ weniger ein Problem darstellt.

Was ist die idiomatische Lösung in C?

  1. Deklarieren Sie foo als a nehmend char**, und dokumentieren Sie einfach die Tatsache, dass es seine Eingaben nicht ändert? Das scheint ein bisschen eklig zu sein, insb. da es Benutzer bestraft, die eine haben könnten const char** dass sie es bestehen wollen (jetzt müssen sie werfen ein Weg Konstanz)

  2. Zwingen Sie Benutzer, ihre Eingaben zu übertragen, und fügen Sie Konstanz hinzu.

  3. Etwas anderes?

  • 1) const ist eine nutzlose Compiler-Anmerkung in C.

    – Konrad Meyer

    4. März 2011 um 17:13 Uhr


  • @Conrad: Ich weiß nicht, ob ich sagen würde nicht zu gebrauchenaber es scheint ein bisschen … hinfällig

    – jwd

    4. März 2011 um 17:30 Uhr

  • @jw: Sie können es wegwerfen, Sie können es auf nicht-konstante Dinge übertragen … und Sie können konstante Zeiger und nicht-konstante Zeiger haben, die auf dasselbe Objekt im Speicher verweisen – es ist eine schwache Compiler-Anmerkung, die nur stoppt die dümmste versehentliche Verwendung. Die kniffligeren und schwieriger zu entdeckenden Fehler bleiben völlig unentdeckt.

    – Konrad Meyer

    4. März 2011 um 17:41 Uhr

  • Und der Vollständigkeit halber beachten Sie gemäß einem Ihrer Links, dass Sie in C++ in der Lage sein sollten, “char **” “char const * const *” umzuwandeln, da dieser Fehler nicht auftritt, also ist dies wahrscheinlich die Theorie richtigen Weg, um die Funktion zu deklarieren, wenn Sie sie wirklich brauchen. Offensichtlich hilft das nicht, wenn Sie striktes C verwenden, aber wenn Sie C gemischt mit C++ oder einem Compiler verwenden, der das als Erweiterung aktivieren kann (falls vorhanden?), kann dies eine ausreichende Wahl sein.

    – Jack V.

    11. Mai 2011 um 12:40 Uhr

Benutzer-Avatar
Jens Gustedt

Obwohl Sie bereits eine Antwort akzeptiert haben, möchte ich 3) nämlich Makros wählen. Sie können diese so schreiben, dass der Benutzer Ihrer Funktion nur einen Aufruf schreibt foo(x); wo x sein kann const-qualifiziert oder nicht. Die Idee wäre, ein Makro zu haben CASTIT die die Umwandlung durchführt und prüft, ob das Argument einen gültigen Typ hat, und eine andere, die die Benutzeroberfläche ist:

void totoFunc(char const*const* x);    
#define CASTIT(T, X) (                 \
   (void)sizeof((T const*){ (X)[0] }), \
   (T const*const*)(X)                 \
)
#define toto(X) totoFunc(CASTIT(char, X))

int main(void) {
   char      *     * a0 = 0;
   char const*     * b0 = 0;
   char      *const* c0 = 0;
   char const*const* d0 = 0;
   int       *     * a1 = 0;
   int  const*     * b1 = 0;
   int       *const* c1 = 0;
   int  const*const* d1 = 0;

   toto(a0);
   toto(b0);
   toto(c0);
   toto(d0);
   toto(a1); // warning: initialization from incompatible pointer type
   toto(b1); // warning: initialization from incompatible pointer type
   toto(c1); // warning: initialization from incompatible pointer type
   toto(d1); // warning: initialization from incompatible pointer type
}

Das CASTIT Makro sieht ein bisschen kompliziert aus, aber alles, was es tut, ist zuerst zu prüfen, ob X[0] zuordnungskompatibel ist char const*. Dafür wird ein zusammengesetztes Literal verwendet. Diese ist dann in a versteckt sizeof um sicherzustellen, dass das zusammengesetzte Literal niemals erstellt wird und auch das X wird durch diesen Test nicht bewertet.

Dann folgt ein einfacher Wurf, der allein aber zu gefährlich wäre.

Wie Sie an den Beispielen in der sehen können main dies erkennt genau die fehlerhaften Fälle.

Vieles davon ist mit Makros möglich. Ich habe kürzlich ein kompliziertes Beispiel gekocht mit const-qualifizierte Arrays.

  • Dies ist ein Beispiel dafür, wie C verschleiert wird. Ein späterer Betreuer wird es schwer haben zu verstehen, was hier vor sich geht. Wenn der Name nicht aus Großbuchstaben besteht, würde ich davon ausgehen toto() war ein Funktionsaufruf, es sei denn, überall, wo er verwendet wurde, stand ein Kommentar. Wenn der spätere Betreuer eine Warnung erhalten würde, würde es unangenehm lange dauern, den Grund herauszufinden, da der Compiler Sie nicht über das Makro informiert.

    – atlpeg

    4. März 2011 um 20:41 Uhr

  • @atlpeg: Wenn dich nur stört, dass man nur Großbuchstaben verwenden sollte, ist das keine große Sache, nimm es so TOTO falls Sie es wollen. Für den Rest verstehe ich Ihren Standpunkt, aber ich stimme nicht vollständig zu. Die fehlende Möglichkeit zu werfen char** zu char const*const* ist eindeutig ein Sprachfehler. C++ kommt damit viel besser durch. Benutzer mit den Lösungen 1 oder 2 zu zwingen, viele Umwandlungen einzuführen, ist ein Albtraum und sehr gefährlich, da C nur noch eine Umwandlungsart kennt. Ändert also ein Benutzer den Typ ab char zu int Die Besetzung würde das einfach akzeptieren.

    – Jens Gustedt

    4. März 2011 um 20:53 Uhr


  • Auf jeden Fall interessant. Ich bin mir nicht sicher, ob ich es verwenden werde, aber interessant (:

    – jwd

    7. März 2011 um 17:59 Uhr

  • defekten Link melden! Auch das hat leider das Problem, dass man nicht durchkommt toto als Funktionszeiger.

    – Shahbaz

    17. Dezember 2012 um 15:33 Uhr


  • @Shahbaz, danke für den Bericht, effektiv scheint es, dass Doxygen ihre Namensverfälschung geändert hat. Für die Zeiger, ja, richtig, man kann nicht alles haben. In der Zwischenzeit haben wir jedenfalls gewonnen _Generic mit C11, also würde ich heutzutage wahrscheinlich das für diese Aufgabe verwenden.

    – Jens Gustedt

    17. Dezember 2012 um 15:43 Uhr

2 ist besser als 1. 1 ist jedoch ziemlich üblich, da große Mengen an C-Code const überhaupt nicht verwenden. Wenn Sie also neuen Code für ein neues System schreiben, verwenden Sie 2. Wenn Sie Wartungscode für ein vorhandenes System schreiben, bei dem const eine Seltenheit ist, verwenden Sie 1.

Gehen Sie mit Option 2. Option 1 hat den von Ihnen erwähnten Nachteil und ist weniger typsicher.

Wenn ich eine Funktion gesehen habe, die a braucht char ** Argument und ich habe ein char *const * oder ähnliches, ich würde eine Kopie machen und das weitergeben, nur für den Fall.

  • Gute Antworten – Ich habe euch beiden ein Upboat gegeben und eine Münze für die Antwort geworfen (: Sieht aus, als gäbe es keine Wunderwaffe.

    – jwd

    4. März 2011 um 17:29 Uhr


Benutzer-Avatar
talentlos

Moderne (C11+) Art der Verwendung _Generic um die Typsicherheit und Funktionszeiger beizubehalten:

// joins an array of words into a new string;
// mutates neither *words nor **words
char *join_words (const char *const words[])
{
// ...
}

#define join_words(words) join_words(_Generic((words),\
          char ** : (const char *const *)(words),\
    char *const * : (const char *const *)(words),\
          default : (words)\
))

// usage :
int main (void)
{
    const char *const words_1[] = {"foo", "bar", NULL};
    char *const words_2[] =       {"foo", "bar", NULL};
    const char *words_3[] =       {"foo", "bar", NULL};
    char *words_4[] =             {"foo", "bar", NULL};

// none of the calls generate warnings:
    join_words(words_1);
    join_words(words_2);
    join_words(words_3);
    join_words(words_4);

// type-checking is preserved:
    const int *const numbers[] = { (int[]){1, 2}, (int[]){3, 4}, NULL };
    join_words(numbers);
// warning: incompatible pointer types passing
// 'const int *const [2]' to parameter of type 'const char *const *'

// since the macro is defined after the function's declaration and has the same name,
// we can also get a pointer to the function
    char *(*funcptr) (const char *const *) = join_words;
}

1346300cookie-checkidiomatisches C für konstante Doppelzeiger

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

Privacy policy