C中的"私有"结构成员与const

Mou*_*Man 9 c struct const private

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

这是一个想法,它提供了一种方法,使编译器抱怨对"私有"成员的无效访问,只需要几个额外的代码.我们的想法是定义两次相同的结构,但为模块的用户添加了一些额外的"const".

当然,使用演员阵容仍然可以写"私人"成员.但关键是要避免模块用户的错误,而不是安全地保护内存.

/*** 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 = %d\n", pt->x, pt->y);  /* no accessor needed for reading "private" members */

    delete_2dPoint(&pt);

    return EXIT_SUCCESS;
}
Run Code Online (Sandbox Code Playgroud)

而现在,问题是:这个技巧可以用C标准吗?它适用于GCC,编译器不会抱怨任何东西,即使有一些严格的标志,但我怎么能确定这是真的好吗?

小智 7

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

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

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

  • @ H2CO3:C标准中没有像const限定对象那样的东西.有const限定类型.有一条规则禁止访问使用const限定类型定义的对象,其中左值为非const限定类型,但这不适用于此处,因为该对象将使用非const类型定义,而const类型将仅用于访问.引用违反C的确切规则. (3认同)
  • @ H2CO3,它不是我,但也受到了诱惑.你的答案只是在虚假的边界上是不精确的.C中的"标识符"肯定没有类型或者可以是"const"限定的.对象有一个类型,可以是`const`限定的,并且它是UB来访问`const`-qualified类型的对象.所以这个规则本身不会在问题UB中采用这种方法.埃里克正在给出正确答案. (2认同)
  • @ H2CO3错了.对象的类型在编译单元中确定,其中没有`const`,因此对象的类型不是`const`-qualified.因此将这样的对象传递回该编译单元以修改它是有效的.再说一遍,你的想法为什么这将是UB是错的,埃里克给出了正确的答案,你现在正在复制它,而没有提到埃里克的. (2认同)
  • [纠正错字.] @ H2CO3:你是否撤回了你的陈述"因为对象是const限定的并且它被[修改],那就是调用UB的东西"?因为对象不是const限定的(因为没有这样的东西),并且对象不是由const限定类型定义的,所以修改它本身并不是未定义的行为.如果您没有收回它,请说明违反C标准的特定条款. (2认同)

Eri*_*hil 3

这违反了 C 2011 6.2.7 1。

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

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

这是非常不正确的编程。

  • @H2CO3,你能解释得更好一点吗?与您的答案相反,这里给出了正确的答案,包括对显示为什么这是 UB 的标准的引用。 (2认同)