"Private" C 中的结构成员与 const



为了有一个干净的代码,使用一些OO概念可能是有用的,即使在C中也是如此。我经常编写由一对。h和。c文件组成的模块。问题是模块的用户必须小心,因为私有成员在c中不存在。使用pimpl习惯用法或抽象数据类型是可以的,但它增加了一些代码和/或文件,并且需要更重的代码。当我不需要访问器时,我讨厌使用它。

这是一个想法,它提供了一种方法,使编译器报错无效访问"private"成员,只有一些额外的代码。其思想是定义两次相同的结构,但为模块的用户添加了一些额外的'const'。

当然,用"private"成员仍然可能与演员。但重点只是为了避免模块用户的错误,而不是为了安全保护内存。

/*** 2DPoint.h module interface ***/
#ifndef H_2D_POINT
#define H_2D_POINT
/* 2D_POINT_IMPL need to be defined in implementation files before #include */
#ifdef 2D_POINT_IMPL
#define _cst_
#else
#define _cst_ const
#endif
typedef struct 2DPoint
{
    /* public members: read and write for user */
    int x;
    
    /* private members: read only for user */
    _cst_ int y;
} 2DPoint;
2DPoint *new_2dPoint(void);
void delete_2dPoint(2DPoint **pt);
void set_y(2DPoint *pt, int newVal);

/*** 2dPoint.c module implementation ***/
#define 2D_POINT_IMPL
#include "2dPoint.h"
#include <stdlib.h>
#include <string.h>
2DPoint *new_2dPoint(void)
{
    2DPoint *pt = malloc(sizeof(2DPoint));
    pt->x = 42;
    pt->y = 666;
    return pt;
}
void delete_2dPoint(2DPoint **pt)
{
    free(*pt);
    *pt = NULL;
}
void set_y(2DPoint *pt, int newVal)
{
    pt->y = newVal;
}
#endif /* H_2D_POINT */

/*** main.c user's file ***/
#include "2dPoint.h"
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    2DPoint *pt = new_2dPoint();
    pt->x = 10;     /* ok */
    pt->y = 20;     /* Invalid access, y is "private" */    
    set_y(pt, 30);  /* accessor needed */
    printf("pt.x = %d, pt.y = %dn", pt->x, pt->y);  /* no accessor needed for reading "private" members */
    delete_2dPoint(&pt);
    
    return EXIT_SUCCESS;
}

现在,这里有一个问题:这个技巧在C标准中可以吗?它可以很好地与GCC一起工作,编译器不会抱怨任何事情,即使有一些严格的标志,但我怎么能确定这是真的好吗?

这几乎肯定是未定义的行为。

禁止写入/修改声明为const的对象,这样做会导致UB。此外,您采用的方法将struct 2DPoint重新声明为两种技术上不同的类型,这也是不允许的。

注意,这(作为一般未定义的行为)并不意味着它"肯定不会工作"或"它必须崩溃"。事实上,我发现它很合乎逻辑,因为如果一个人聪明地阅读源代码,他可能很容易发现它的目的是什么,为什么它可能被认为是正确的。然而,编译器不是智能的——充其量,它是一个有限的自动机,它不知道代码应该做什么;它只服从(或多或少)语法的语法和语义规则。

这违反了C 2011 6.2.7 1.

6.2.7 1要求同一结构体在不同翻译单元中的两个定义具有兼容类型。不允许在其中一个容器中有const而在另一个容器中没有。

在一个模块中,可能有对这些对象之一的引用,而这些对象的成员在编译器看来似乎是const。当编译器写入对其他模块中的函数的调用时,它可能保存寄存器或其他缓存中const成员的值,或者保存在源代码中部分求值或完全求值的表达式中,而不是函数调用。然后,当函数修改成员并返回时,原始模块将不具有更改后的值。更糟糕的是,它可能会使用更改后的值和旧值的组合。

用Bjarne Stroustrup的话来说:C并没有被设计成支持 OOP,尽管它支持 OOP,这意味着用C编写OOP程序是可能的,但是很难。因此,如果您必须用C编写OOP代码,那么使用这种方法似乎没有什么问题,但是最好使用更适合此目的的语言。

通过尝试用C编写OOP代码,您已经进入了一个必须覆盖"常识"的领域,因此只要您负责正确使用它,这种方法就很好。您还需要确保它是彻底和严格的文档,并且与代码相关的每个人都知道它。

编辑哦,您可能必须使用强制转换来绕过const。我不记得C风格的强制转换是否可以像c++ const_cast那样使用。

您可以使用不同的方法-声明两个struct,一个用于没有私有成员的用户(在头文件中),一个具有私有成员供内部使用在您的实现单元。所有的private成员都应该放在public成员之后。

总是传递指针到struct,并在需要时将其强制转换为内部使用,如:

/* user code */
struct foo {
    int public;
};
int bar(void) {
    struct foo *foo = new_foo();
    foo->public = 10;
}
/* implementation */
struct foo_internal {
    int public;
    int private;
};
struct foo *new_foo(void) {
    struct foo_internal *foo == malloc(sizeof(*foo));
    foo->public = 1;
    foo->private = 2;
    return (struct foo*)foo;  // to suppress warning
}

C11允许未命名的结构字段(GCC有时支持),因此在使用GCC(或C11兼容的编译器)的情况下,您可以将内部结构声明为:

struct foo_internal {
    struct foo;
    int private;
};

因此不需要额外的努力来保持结构定义同步。

最新更新