const关键字的这种用法是否符合其意图?

时间:2019-01-17 19:26:46

标签: c struct const

我正在设计一个API,并正在考虑使用“不可变”结构”或“只读结构”。使用一个简化的示例,它可能类似于:

struct vector {
    const float x;
    const float y;
};

struct vector getCurrentPosition(void);
struct vector getCurrentVelocity(void);

只要我在堆栈上返回不变结构,一切都可以正常工作。但是,在实现以下功能时遇到问题:

void getCurrentPositionAndVelocity(
    struct vector *position,
    struct vector *velocity);

在这一点上,我不想引入一个包含两个向量的新的不可变结构。我也无法做到这一点:

void
getCurrentPositionAndVelocity(
    struct vector *position,
    struct vector *velocity)
{
    *position = getCurrentPosition();
    *velocity = getCurrentVelocity();
}

因为positionvelocity是只读的(尽管我安装的clang incorrectly compiles this without warning版本)。

我可以使用memcpy来解决此问题,如下所示:

void
getCurrentPositionAndVelocity(
    struct vector *position,
    struct vector *velocity)
{
    struct vector p = getCurrentPosition();
    struct vector v = getCurrentVelocity();

    memcpy(position, &p, sizeof *position);
    memcpy(velocity, &v, sizeof *velocity);
}

这看起来很糟糕,但我相信它不会误导API用户,vector结构看起来仍然是不变的。为此,我可以另外添加一个必需的初始化程序,在该初始化程序中,仅对具有特殊值的vector结构成功调用此类函数。像这样:

const struct vector * const VECTOR_UNINITIALIZED;

用户应该去的地方

struct vector position = *VECTOR_UNINITIALIZED;
struct vector velocity = *VECTOR_UNINITIALIZED;

在调用getCurrentPositionAndVelocity()之前。在memcpy插入之前,该实现将使用memcmp断言向量确实具有“未初始化的哨兵”值。 (为此,我意识到只有在某些确实未使用的值可以充当“未初始化的标记”值的情况下,此方法才有效,但我认为情况就是如此。)

我的问题是,从API用户的角度来看,const关键字的用法是否符合其意图?从编译器的角度来看,是否存在任何风险,因为严格来讲,该代码可能违反了const关键字所指示的只读语义?作为API用户,您对这种方法有什么看法?或者您有什么更好的建议?

2 个答案:

答案 0 :(得分:3)

TL; DR

  

const关键字的这种用法是否符合其意图?

否。


  

我的问题是const关键字的用法是否符合   从API用户的角度来看,其意图是什么?

我不确定API用户的观点。实际上,您提出的设计似乎可能比平时产生更大的用户视角多样性,因为明显的预期行为与C语言要求不一致。

  

会不会有任何   从编译器的角度来看存在风险,因为该代码可能会   严格来讲,违反了只读语义,如   const关键字?

是的。具体来说,


  

如果试图修改用   通过使用带有非const限定的左值的const限定类型   类型,行为是不确定的。

C2011, 6.7.3/6


发生UB时,编译器可能会做各种有趣的事情。更有可能的是,他们可能会假设某些未定义的行为不会发生。从而, 例如,当编译一个调用您的getCurrentPositionAndVelocity()函数的程序时,编译器可能会假定该函数未修改提供给它的结构的const成员。否则尝试修改它们实际上可能会失败。或其他任何东西,实际上是“未定义”的意思。

  

作为API用户,您对此有何看法   方法还是您有什么更好的建议?

我认为我的API提供者应该尽力提供符合语言标准的实现。

此外,我想知道您认为谁通过使结构成员成为const来保护我免受保护,为什么您认为您比我更了解是否应该修改结构成员。我也想知道,为什么有人会认为这种保护足够重要,足以保证参加const结构成员的所有很多问题。

关于更好的建议,只是不让成员成为const会怎么样?它将为您和您的用户节省痛苦。


关于修改,其中添加了对How to initialize const members of structs on the heap的引用,就标准而言,此处讨论的案例与此处考虑的案例有很大不同。动态分配的内存没有声明的类型,但根据已写入的内容和/或访问方式,可能具有有效类型。标准的paragraph 6.5/6中介绍了有关规则,您可以在SO上其他位置的几个答案中找到有关此问题的讨论,例如您在评论中链接的this one

最重要的是,分配了生存期的对象会从写入的第一个数据中获取其有效类型,并且该首次写入可被视为其有效的初始化(尽管在标准中未使用后者)。随后的操作必须遵守有效类型,包括根据类型paragraph 6.5/7递归地称为{严格别名规则”的约束,包括由于类型本身或其成员的const性引起的约束。

具有静态或自动生存期的对象将其声明的类型作为其有效类型,并具有其初始值设定项(如果有)指定的初始值。它们也必须以与其有效类型一致的方式进行操作。

答案 1 :(得分:-1)

memcpy(position, &p, sizeof *position);
memcpy(velocity, &v, sizeof *velocity);

您滥用了与编译器的合同。您声明了const,然后尝试解决。 极端不好的做法

只要您不想违反此协议,就不要将结构成员声明为const