sirven que punteros parametros para los funciones estructura dobles declaracion datos como cadenas arreglos apuntadores c const idioms idiomatic

que - C idiomática para los punteros dobles const.



punteros dobles en c (3)

Soy consciente de que en C no se puede convertir implícitamente, por ejemplo, char** a const char** (cf C-Faq , SO pregunta 1 , SO pregunta 2 ).

Por otro lado, si veo una función declarada así:

void foo(char** ppData);

Debo asumir que la función puede cambiar los datos pasados. Por lo tanto, si estoy escribiendo una función que no cambiará los datos, es mejor, en mi opinión, declarar:

void foo(const char** ppData);

o incluso:

void foo(const char * const * ppData);

Pero eso pone a los usuarios de la función en una posición incómoda. Ellos pueden tener:

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

Y para llamar limpiamente a mi función, tendrían que insertar un modelo.

Vengo de un fondo mayormente de C ++, donde este es un problema menor debido a las reglas constantes más profundas de C ++.

¿Cuál es la solución idiomática en C?

  1. Declara foo como tomando un char** , y solo documenta el hecho de que no cambiará sus entradas? Eso parece un poco asqueroso, esp. ya que castiga a los usuarios que pueden tener un const char** que quieren pasarlo (ahora tienen que deshacerse de la constancia)

  2. Forzar a los usuarios a emitir su entrada, agregando constancia.

  3. ¿Algo más?


Aunque ya ha aceptado una respuesta, me gustaría ir por 3) macros. Puede escribirlos de manera que el usuario de su función simplemente escriba una llamada foo(x); donde x puede ser const cualificado o no. La idea sería tener una macro CASTIT que CASTIT la CASTIT y verifique si el argumento es de un tipo válido, y otra que sea la interfaz de usuario:

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 }

La macro CASTIT parece un poco complicada, pero todo lo que hace es verificar primero si X[0] es una asignación compatible con char const* . Utiliza un literal compuesto para eso. Esto se oculta dentro de un sizeof para asegurar que en realidad nunca se crea el literal compuesto y que esa prueba no evalúa a X

Luego sigue un molde llano, pero que por sí mismo sería demasiado peligroso.

Como puede ver en los ejemplos main esto detecta exactamente los casos erróneos.

Muchas de esas cosas son posibles con macros. Hace poco cociné un ejemplo complicado con arreglos const .


Sin embargo, 2 es mejor que 1. 1 es bastante común, ya que los grandes volúmenes de código C no usan const en absoluto. Entonces, si está escribiendo un nuevo código para un nuevo sistema, use 2. Si está escribiendo un código de mantenimiento para un sistema existente donde const es una rareza, use 1.


Vaya con la opción 2. La opción 1 tiene la desventaja que mencionó y es menos segura para el tipo.

Si veo una función que toma un argumento char ** y tengo un char *const * o similar, haría una copia y la aprobaría, por si acaso.