¿Puede GCC advertirme sobre la modificación de los campos de una estructura en C99?
const compiler-warnings (7)
¿Puede GCC advertirme sobre la modificación de los campos de una estructura en C99?
No estás modificando los campos de una estructura const.
Un valor de estructura A contiene un puntero a un carácter no const. ptrA es un puntero a una estructura A. Entonces, no puede cambiar el valor de la estructura A en * ptrA. Por lo tanto, no puede cambiar el puntero a char en (* ptrA) .Char alias ptrA-> ptrChar. Pero está cambiando el valor en donde ptrA-> ptrChar señala, es decir, el valor en * (ptrA-> Char) aka ptrA-> Char [0]. Las únicas ventajas aquí son struct As y no estás cambiando una estructura A, entonces, ¿qué es exactamente "no deseado"?
Si no desea permitir el cambio al valor en el que apunta el campo Char de una estructura A (a través de esa estructura A), utilice
struct A
{
const char *ptrChar; // or char const *ptrChar;
};
Pero tal vez lo que piensas que estás haciendo en f es algo así como
void f(const struct A *ptrA)
{
const char c = ''A'';
ptrA->ptrChar = &c;
}
Que obtendrá un error del compilador.
Me encontré con un pequeño problema al intentar hacer un código correcto.
Me hubiera gustado escribir una función que toma un puntero a una construcción, decirle al compilador "por favor dígame si estoy modificando la estructura, porque realmente no quiero".
De repente, me vino a la mente que el compilador me permitirá hacer esto:
struct A
{
char *ptrChar;
};
void f(const struct A *ptrA)
{
ptrA->ptrChar[0] = ''A''; // NOT DESIRED!!
}
Lo cual es comprensible, porque lo que realmente es const es el puntero en sí, pero no el tipo al que apunta. Me gustaría que el compilador me diga que estoy haciendo algo que no quiero hacer, si eso es posible.
Usé gcc como mi compilador. Aunque sé que el código anterior debería ser legal, de todos modos verifiqué si emitiría una advertencia, pero no ocurrió nada. Mi línea de comando era:
gcc -std=c99 -Wall -Wextra -pedantic test.c
¿Es posible evitar este problema?
Este es un ejemplo de implementación vs. interfaz, o "ocultamiento de información" - o más bien no ocultación ;-) - problema. En C ++, uno simplemente tendría el puntero privado y definiría los accesos de const pública adecuados. O uno definiría una clase abstracta, una "interfaz", con el descriptor de acceso. La estructura apropiada implementaría eso. Los usuarios que no necesitan crear instancias de estructura solo necesitarán ver la interfaz.
En C uno podría emular eso definiendo una función que toma un puntero a la estructura como parámetro y devuelve un puntero a const char. Para los usuarios que no crean instancias de estas estructuras, incluso se podría proporcionar un "encabezado de usuario" que no filtre la implementación de la estructura, sino que solo defina funciones de manipulación que toman (o regresan, como una fábrica) punteros. Esto deja a la estructura como un tipo incompleto (por lo que solo se pueden usar punteros a las instancias). Este patrón emula efectivamente lo que hace C ++ detrás de las escenas con this
puntero.
Este es un problema conocido del lenguaje C y no se puede evitar. Después de todo, no está modificando la estructura, está modificando un objeto separado a través de un puntero no const
qualificado que obtuvo de la estructura. const
semántica se diseñó originalmente en torno a la necesidad de marcar las regiones de memoria como constantes que físicamente no se pueden escribir, y no en torno a ninguna preocupación por la programación defensiva.
No, a menos que cambie la definición de estructura a:
struct A
{
const char *ptrChar;
};
Otra solución intrincada que mantiene la definición de estructura anterior intacta, es definir una nueva estructura con miembros idénticos, cuyos miembros de puntero relevantes se establecen en: puntos a tipo const. Entonces la función a la que llamas cambia para tomar la nueva estructura. Se define una función de envoltura que toma la estructura anterior, hace que un miembro por miembro copie a la nueva estructura y la pasa a la función.
Podríamos ocultar la información detrás de algunas funciones de "acceso":
// 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; }
Tal vez, si decide utilizar C11, puede implementar una macro Genérica que se refiere a la versión constante o variable del mismo miembro (también debe incluir una unión en su estructura). Algo como esto:
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!!
}
La unión crea 2 interpretaciones para un solo miembro. El m_cptrChar
es un puntero a char constante y el m_ptrChar
a no constante. Luego, la macro decide a cuál referirse dependiendo del tipo de su parámetro.
El único problema es que la macro ptrChar_m
solo puede funcionar con un puntero u objeto de esta estructura y no con ambos.
Una forma de diseñar su camino, si es necesario, es usar dos tipos diferentes para el mismo objeto: un tipo de lectura / escritura y uno de solo lectura.
typedef struct
{
char *ptrChar;
} A_rw;
typedef struct
{
const char* ptrChar;
} A_ro;
typedef union
{
A_rw rw;
A_ro ro;
} A;
Si una función necesita modificar el objeto, toma el tipo de lectura-escritura como parámetro; de lo contrario, toma el tipo de solo lectura.
void modify (A_rw* a)
{
a->ptrChar[0] = ''A'';
}
void print (const A_ro* a)
{
puts(a->ptrChar);
}
Para mejorar la interfaz de la persona que llama y hacerla coherente, puede usar funciones de envoltura como la interfaz pública para su ADT:
inline void A_modify (A* a)
{
modify(&a->rw);
}
inline void A_print (const A* a)
{
print(&a->ro);
}
Con este método, A
ahora se puede implementar como tipo opaco, para ocultar la implementación de la persona que llama.