organizacional - ¿Un tipo con 2 definiciones de comportamiento indefinido en C?
tipos de comportamiento organizacional (2)
Estoy usando este enfoque hasta ahora y funciona bien, sin ningún comportamiento indefinido
Supongo que quiere decir que el código que presenta muestra el comportamiento observable que espera en las circunstancias en las que lo ha probado, lo cual es muy diferente de estar sin un comportamiento indefinido. Ciertamente, el código como lo publicó originalmente tuvo un comportamiento indefinido como resultado de la aplicación del operador sizeof
a una expresión de tipo void
.
Pero primero, sizeof (void *) y sizeof (Point *) son siempre los mismos
C no garantiza eso, ni que las representaciones de esos tipos de punteros sean equivalentes. Sin embargo, puede convertir de forma segura un Point *
en un void *
y viceversa, donde "de forma segura" significa que el resultado se comparará igual al Point *
original Point *
.
Me gustaría usar este enfoque mostrando
Point_p
a lib.c comotypedef Point* Point_p
y a todos los demás archivos que no forman parte de la biblioteca comotypedef void* Point_p
.
Esto no es seguro y formalmente exhibiría un comportamiento indefinido, que podría o no manifestarse de una manera que usted note. Aunque puede convertir entre ellos, Point *
y void *
no son tipos "compatibles" en el sentido estándar del término.
Un mejor patrón para implementar tipos opacos en C es usar tipos incompletos . Eso se vería algo así:
lib.h:
// User header for lib
#ifndef __LIB_H
#define __LIB_H
// Structure for a single point -- NO BODY DECLARED
typedef struct point Point;
// Create point
Point *createPoint(int x, int y);
#endif
lib.c:
#include <stdlib.h>
#include "lib.h"
// complete the definition of struct point
struct point {
int x, y;
};
Point *createPoint(int x, int y) {
Point *p = malloc(sizeof(*p));
p->x = x;
p->y = y;
return p;
}
Con eso, no tiene macros desordenadas que controlen qué partes del encabezado se deben usar, y ni siquiera tiene que preocuparse por el código del cliente simplemente declarando __LIB_INTERNAL
para obtener acceso a los miembros de la estructura, porque no están en el encabezado en absoluto . Sin embargo, todo esto tiene un comportamiento perfectamente definido y, además, una mejor seguridad de tipo que el uso de void *
para todo.
Considera una biblioteca donde tengas algún código. Por ejemplo, hagamos algún punto X e Y manipulación.
Y luego construyes tu biblioteca donde no quieres permitir que los usuarios accedan a tu variable de estructura, hasta ahora estoy usando este enfoque y parece funcionar bien.
lib.h:
#ifndef __LIB_H
#define __LIB_H
#ifdef __LIB_INTERNAL
//Structure for single point
typedef struct {
int x, y;
} Point;
//Casted pointer
#define _P(in) ((Point *)(in))
#endif
//Define pointer for public use as void pointer
typedef void* Point_p;
//Create point
Point_p createPoint(int x, int y);
#endif
lib.c:
//Define LIB_INTERNAL to allow visible access
#define __LIB_INTERNAL
#include "lib.h"
#include "stdlib.h"
Point_p createPoint(int x, int y) {
Point_p p = malloc(sizeof(Point));
_P(p)->x = x; //_P is visible in this function
_P(p)->y = y;
return p;
}
C Principal:
#include "lib.h"
int main() {
Point_p p = createPoint(1, 2); //OK
Point *ptr = createPoint(1, 2); //Error as Point is not visible public
p->x = 4; //Error as Point_p is void *
}
De esta manera, me estoy asegurando de que el usuario no tenga acceso directo a la variable Punto y se vea obligado a usar funciones para realizar operaciones en este punto.
Ahora estoy pensando en otro enfoque. Pero primero, sizeof(void *)
y sizeof(Point *)
es siempre el mismo, así que me gustaría usar este enfoque mostrando Point_p
a lib.c
como typedef Point* Point_p
y a todos los demás archivos que no forman parte de la biblioteca como typedef void* Point_p
.
lib.h
#ifndef __LIB_H
#define __LIB_H
#ifdef __LIB_INTERNAL
//Structure for single point
typedef struct {
int x, y;
} Point;
//Define pointer for private use as Point pointer
typedef Point* Point_p;
#else
//Define pointer for public use as void pointer
typedef void* Point_p;
#endif
//Create point
Point_p createPoint(int x, int y);
#endif
lib.c:
//Define LIB_INTERNAL to allow visible access
#define __LIB_INTERNAL
#include "lib.h"
#include "stdlib.h"
Point_p createPoint(int x, int y) {
Point_p p = malloc(sizeof(Point));
p->x = x; //_P is not needed because Point_p is visible as Point *
p->y = y;
return p;
}
main.c: igual que el anterior
Pregunta
¿Es este comportamiento indefinido? Debido a que, en segundo lugar, lib.c
ve a Point_p
como Point *
, pero main.c
todavía lo ve como void *
y, por lib.c
tanto, lib.c
tiene acceso a los miembros directamente sin lanzar antes y main.c
no lo tiene. la estructura esta oculta
Sí lo es. No se garantiza que los punteros Struct tengan la misma representación que los punteros nulos.
Sin embargo, se garantiza que todos los punteros de estructura tienen la misma representación independientemente de la etiqueta,
6.2.5p28 :
... Todos los punteros a los tipos de estructura deben tener los mismos requisitos de representación y alineación que los demás. Todos los punteros a los tipos de unión deben tener los mismos requisitos de representación y alineación que los demás. ...
por lo tanto, la forma común y bien definida de resolver esto es proporcionar solo una declaración hacia adelante de una estructura en el encabezado público y luego usar punteros para eso.
public_header.h
struct Point; //the private header provides the full definition
struct Point* createPoint(int x, int y);
//...
private_header:
#include "public_header.h"
struct Point { int x, y; }; //full definition
Ese enfoque tampoco sufre de la holgura de tipo de los punteros de void
.
(También debe evitar usar identificadores que comiencen con dos guiones bajos o un guión bajo y una letra mayúscula, así como identificadores / etiquetas de telescopios que comiencen con un guión bajo (nunca empiece los identificadores con un guión bajo si quiere que sea sencillo). comportamiento indefinido también (ver 7.1.3 identificadores reservados )).