Warum erlaubt C keine implizite Konvertierung von char ** in const char *const * (und C++ tut es)?

Warum erlaubt C keine implizite Konvertierung von char ** in const char *const * (und C++ tut es)?


Ich kenne die implizite Konvertierung von char ** bis const char ** nicht möglich ist und warum, und dass die Umstellung auf char *const * funktioniert. Siehe unten für Links zu Erläuterungen dazu.


Es macht alles Sinn, abgesehen von einer bestimmten Sache. Also habe ich den folgenden Code:


#include <stdio.h>
void
print(const char *const*param)
{
printf("%s\n", param[0]);
}
int
main(int argc, char **argv)
{
print(argv);
return 0;
}

Wenn ich dies als C++-Code kompiliere, kompiliert es ganz gut. Wenn derselbe Code jedoch nur als C-Code kompiliert wird, erhalte ich einen Fehler (naja, eine Warnung, aber nehmen wir an, -Werror , d.h. Warnungen als Fehler behandeln).


gcc:


test.c: In function ‘main’:
test.c:12:11: warning: passing argument 1 of ‘print’ from incompatible pointer type [-Wincompatible-pointer-types]
print(argv);
^
test.c:4:1: note: expected ‘const char * const*’ but argument is of type ‘char **’
print(const char *const*param)
^

klingeln:


test.c:12:11: warning: passing 'char **' to parameter of type 'const char *const *' discards qualifiers in nested pointer types [-Wincompatible-pointer-types-discards-qualifiers]
print(argv);
^~~~
test.c:4:25: note: passing argument to parameter 'param' here
print(const char *const*param)
^

Beide Verhaltensweisen sind standardunabhängig und auch Compiler-unabhängig. Ich habe verschiedene Standards mit beiden gcc ausprobiert und clang .


Diese Anfrage hat zwei Gründe. Erstens möchte ich verstehen, ob es einen Unterschied gibt, und zweitens habe ich eine Funktion, die nichts mit irgendeiner Ebene der Zeiger macht, und ich brauche sie, um mit const char ** arbeiten zu können sowie char *const * und char ** . Das explizite Casting jedes Anrufs ist nicht verwaltbar. Und ich habe keine Ahnung, wie der Funktionsprototyp aussehen soll.



Diese Frage hat meine Neugier geweckt:
Implizite Konvertierung von char** in const char**


Und hier ist noch eine nette Erklärung für die char ** => const char** Problem:
http://c-faq.com/ansi/constmismatch.html


Wenn die Links im Zusammenhang mit dieser Frage verwirrend sind, können Sie sie gerne bearbeiten.


Antworten:


C und C++ unterscheiden sich in dieser Hinsicht. Ich habe keine Antwort darauf, warum C++ großzügiger ist, außer dass das C++-Verhalten meiner Meinung nach korrekt ist.


C erlaubt einfach kein indirektes const Wandlung. Das ist eine konservative, einfach zu implementierende Einschränkung, mit der unglücklichen Folge, dass Sie char*[] nicht angeben können zu einer Funktion, die char const* const* erwartet . Die Einschränkung befindet sich in §6.3.2.3, Absatz 2, und sie ist einfach nicht rekursiv:



C++ erlaubt Konvertierungen nach einer etwas komplexen Formulierung in §4.4 [conv.qual], Absatz 3. Es ist erlaubt zu konvertieren


T cvn Pn-1cvn-1 … P1cv1 P0cv0

T cv'n Pn-1cv'n-1 … P1cv'1 P0cv'0


(wobei T ist ein Typ; P1…Pn sind Konstruktoren vom Typ Zeiger/Array, und jeder cv0…cvn ist eine möglicherweise leere Teilmenge von const und volatile )


vorausgesetzt, dass:



  1. Für alle k > 0 , cvk ist eine Teilmenge von cv'k (Sie können also einen const nicht entfernen oder ein volatile ) und



  2. Wenn cvk und cv'k unterscheiden sich für einige k > 0 , alle folgenden cv'i>k schließen Sie const ein .




Im eigentlichen Standard ist dieser Ausdruck umgekehrt; Ich habe es in der Reihenfolge der Deklaration angeordnet, während es im Standard in der Reihenfolge der Anwendung der Zeiger/Array-Konstruktoren ist. Allerdings habe ich die Richtung der Nummerierung nicht geändert, weshalb sie von rechts nach links nummeriert sind. Ich habe auch einige Details weggelassen – zum Beispiel ist es für die beiden T nicht unbedingt notwendig s identisch sein – aber ich denke, es gibt eine Vorstellung von der Absicht.


Die Erklärung für die erste Einschränkung ist ziemlich offensichtlich. Die zweite Einschränkung verhindert das in der C-FAQ beschriebene Problem, wo ein const Zeiger könnte in einem Nicht-const gespeichert werden Pointer-Objekt, und anschließend verwendet, um den const zu mutieren Objekt, auf das es zeigt.


Unterm Strich ist das in C++ Ihr Prototyp const char *const * param funktioniert mit Argumenten vom Typ char** , const char** , oder sogar char*const* , aber in C funktioniert nur das letzte ohne Vorwarnung, und es ist am wenigsten nützlich. Die einzige mir bekannte Problemumgehung (außer dem Wechsel zu C++) besteht darin, die Warnung zu ignorieren.


Für das, was es wert ist, gibt es einen Hinweis im Begründungsabschnitt der Posix-Spezifikation des exec* Interfaces über das Problem, das dies für diese Prototypen verursacht, und die von Posix gewählte Problemumgehung, die darin besteht, char*[] zu verwenden als Prototyp und beachten Sie im Text, dass diese konstant sind:(Hervorhebung hinzugefügt)



Nach diesem Absatz befindet sich eine nützliche Kompatibilitätstabelle, die ich aufgrund der Formatierungsbeschränkungen dieser Site nicht zitiert habe.


Einige Code-Antworten


#include <stdio.h>
void print(const char *const*param) {
printf("%s\n", param[0]);
} int main(int argc, char **argv) {
print(argv);
return 0;
}
test.c: In function ‘main’: test.c:12:11: warning: passing argument 1 of ‘print’ from incompatible pointer type [-Wincompatible-pointer-types]
print(argv);^ test.c:4:1: note: expected ‘const char * const*’ but argument is of type ‘char **’ print(const char *const*param) ^
test.c:12:11: warning: passing 'char **' to parameter of type 'const char *const *' discards qualifiers in nested pointer types [-Wincompatible-pointer-types-discards-qualifiers]
print(argv);
^~~~ test.c:4:25: note: passing argument to parameter 'param' here print(const char *const*param) ^