C中有一个具有2个定义未定义行为的类型吗?

til*_*z0R 22 c

考虑一个你有一些代码的库.例如,让我们做一些X和Y点操作.

然后你构建你的库,你不希望允许用户访问你的struct变量,到目前为止我使用这种方法,它似乎工作正常.

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
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

main.c中:

#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 *
}
Run Code Online (Sandbox Code Playgroud)

这样我就确保用户不能直接访问Point变量,并且强制使用函数来执行此点的操作.


现在我想到另一种方法.但首先,sizeof(void *)并且sizeof(Point *)总是一样的,所以我想通过Point_plib.cas typedef Point* Point_p和所有其他不属于库的文件显示来使用这种方法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
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

main.c:和以前一样


这是未定义的行为吗?因为在第二种方法中,lib.c认为Point_p作为Point *,但main.c仍把它作为void *,因此lib.c访问成员的情况下直接铸造前,main.c没有它也不能投,因为Point结构是隐藏的.

PSk*_*cik 25

是的.不保证struct指针与void指针具有相同的表示形式.

但是,无论标记如何,所有结构指针都保证具有相同的表示形式,

6.2.5p28:

...所有指向结构类型的指针都应具有相同的表示和对齐要求.所有指向union类型的指针都应具有相同的表示和对齐要求....

因此,解决此问题的常见,明确定义的方法是仅在公共头中提供结构的前向声明,然后使用指针.

public_header.h

struct Point; //the private header provides the full definition
struct Point* createPoint(int x, int y);
//...
Run Code Online (Sandbox Code Playgroud)

private_header:

#include "public_header.h"
struct Point { int x, y; }; //full definition
Run Code Online (Sandbox Code Playgroud)

这种方法也不会受到void指针类型松散的影响.

(您还应该避免使用以两个下划线或下划线和大写字母开头的标识符以及以下划线开头的filescope标识符/标记(如果您想保持简单,请不要使用下划线启动标识符) - 这是未定义的行为(参见7.1.3保留标识符)).

  • @ Jean-BaptisteYunès所有数据(不是函数指针)指针转换为void指针而不需要显式转换,但这并不意味着它们可以直接重新解释为void指针,反之亦然.允许表示不同(尽管它们很可能不会). (3认同)
  • @让BaptisteYunès.虽然转换指针会很好,但是如果表示不相同,则将格式化为void指针的对象传递给期望结构指针的函数将导致问题. (2认同)
  • @ Jean-BaptisteYunès那没关系,但是你不应该在一个源中用外部链接声明`T*p;`然后在另一个源中将它称为`void*p`. (2认同)

Joh*_*ger 11

到目前为止,我正在使用这种方法,它可以正常运行,没有任何未定义的行为

我想你的意思是你所呈现的代码展示了你在测试它的情况下所期望的可观察行为,这与没有未定义的行为完全不同.当然,由于将sizeof运算符应用于类型的表达式,因此您最初发布它的代码绝对具有未定义的行为void.

但首先,sizeof(void*)和sizeof(Point*)始终相同

C不保证,也不保证这些指针类型的表示是等价的.但是,您可以安全地 a 转换Point *为a void *和back,其中"安全"意味着结果将与原始值进行比较Point *.

我想使用这种方法,Point_p向lib.c 显示as typedef Point* Point_p和所有其他不属于库的文件typedef void* Point_p.

这是不安全的,并且正式会出现未定义的行为,这可能会或可能不会以您注意到的方式表现出来.即使你可以在它们之间进行转换,Point *并且void *在标准的术语意义上也不是"兼容"类型.

在C中实现opaque类型的更好模式是使用不完整类型.这看起来像这样:

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
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

有了这个,你没有任何凌乱的宏控制标题的哪些部分应该被使用,你甚至不必担心客户端代码只是声明__LIB_INTERNAL访问结构成员,因为它们不在总而言之.尽管如此,这一切都具有完美明确的行为,而且,比使用void *一切更好的类型安全性.