繁体   English   中英

如何在 C 中的结构中初始化 const(使用 malloc)

[英]How to initialize const in a struct in C (with malloc)

我试过了;

void *malloc(unsigned int);
struct deneme {
    const int a = 15;
    const int b = 16;
};

int main(int argc, const char *argv[])
{
    struct deneme *mydeneme = malloc(sizeof(struct deneme));
    return 0;
}

这是编译器的错误:

gereksiz.c:3:17: error: expected ':', ',', ';', '}' or '__attribute__' before '=' token

而且,还有这个;

void *malloc(unsigned int);
struct deneme {
    const int a;
    const int b;
};

int main(int argc, const char *argv[])
{
    struct deneme *mydeneme = malloc(sizeof(struct deneme));
    mydeneme->a = 15;
    mydeneme->b = 20;
    return 0;
}

这是编译器的错误:

gereksiz.c:10:5: error: assignment of read-only member 'a'
gereksiz.c:11:5: error: assignment of read-only member 'b'

两者都没有被编译。 使用 malloc 分配内存时,有什么方法可以在结构内初始化 const 变量?

您需要抛弃 const 来初始化 malloc 结构的字段:

struct deneme *mydeneme = malloc(sizeof(struct deneme));
*(int *)&mydeneme->a = 15;
*(int *)&mydeneme->b = 20;

或者,您可以创建结构的初始化版本并对其进行 memcpy:

struct deneme deneme_init = { 15, 20 };
struct deneme *mydeneme = malloc(sizeof(struct deneme));
memcpy(mydeneme, &deneme_init, sizeof(struct deneme));

如果您deneme_init这样做,您可以将deneme_init静态和/或全局(因此它只需要构建一次)。


使用 C11 标准参考解释为什么此代码不是某些注释所建议的未定义行为:

  • 此代码不违反 6.7.3/6,因为malloc返回的空间不是“使用 const 限定类型定义的对象”。 表达式mydeneme->a不是一个对象,它是一个表达式。 虽然它有const限定类型,但它表示一个没有用 const 限定类型定义的对象(实际上,根本没有用任何类型定义)。

  • 写入malloc分配的空间永远不会违反严格的别名规则,因为每次写入都会更新有效类型(6.5/6)。

(但是,从malloc分配的空间中读取可能会违反严格的别名规则)。

在 Chris 的代码示例中,第一个将整数值的有效类型设置为int ,第二个将有效类型设置为const int ,但是在这两种情况下通过*mydeneme读取这些值都是正确的,因为*mydeneme别名规则(6.5/7 项目符号 2)允许通过与对象的有效类型相同或更多限定的表达式来读取对象。 由于表达式mydeneme->a类型为const int ,因此它可用于读取有效类型intconst int

你有没有试过这样做:

int main(int argc, const char *argv[])
{
    struct deneme mydeneme = { 15, 20 };
    struct deneme *pmydeneme = malloc(sizeof(struct deneme));
    memcpy(pmydeneme, &mydeneme , sizeof(mydeneme));
    return 0;
}

我还没有测试过,但代码似乎是正确的

为了扩展@Chris Dodd 的答案,我一直在阅读标准的“语言律师”详细信息,看来这段代码定义明确:

struct deneme deneme_init = { 15, 20 };
struct deneme *mydeneme = malloc(sizeof(struct deneme));
memcpy(mydeneme, &deneme_init, sizeof(struct deneme));

或者,要动态创建一个完整的 const 限定的结构对象:

const struct deneme deneme_init = { 15, 20 };
struct deneme *mydeneme = malloc(sizeof(struct deneme));
memcpy(mydeneme, &deneme_init, sizeof(struct deneme));

const struct deneme *read_only = mydeneme; 

理由:

在深入了解这一点时,首先需要确定的是,所谓的左值是否具有类型,如果有,该类型是否带有限定符。 这在 C11 6.3.2.1/1 中定义:

左值是可能指定对象的表达式(具有除 void 之外的对象类型); 如果左值在评估时未指定对象,则行为未定义。 当一个对象被称为具有特定类型时,该类型由用于指定该对象的左值指定。 可修改左值是没有数组类型、没有不完整类型、没有常量限定类型的左值,如果它是结构或联合,则没有任何成员(递归地包括任何成员或元素)所有包含的聚合或联合)具有 const 限定类型。

很明显,左值不仅有类型,还有限定符。 如果它是 const 限定的或者如果它是具有 const 限定成员的结构,则它不是可修改的左值。

继续讨论“严格别名”和有效类型的规则,C11 6.5/7:

访问其存储值的对象的有效类型是该对象的声明类型(如果有)。 87)如果一个值通过一个类型不是字符类型的左值存储到一个没有声明类型的对象中,那么左值的类型成为该访问和后续访问的有效类型修改存储的值。 如果使用memcpymemmove将值复制到没有声明类型的对象中,或者复制为字符类型的数组,则该访问和不修改该值的后续访问的修改对象的有效类型是从中复制值的对象的有效类型(如果有)。 对于没有声明类型的对象的所有其他访问,对象的有效类型只是用于访问的左值的类型。

  1. 分配的对象没有声明类型。

这意味着 malloc 返回的分配块没有有效类型,直到通过左值写入访问(通过赋值或memcpy某些内容存储在该内存位置中。 然后它获取该写访问中使用的左值的有效类型。

值得注意的是,指向该内存位置的指针的类型是完全无关的。 它也可能是一个volatile bananas_t*因为它不用于访问左值(至少现在还没有)。 只有用于左值访问的类型才重要。

现在这就是它变得模糊的地方:是否通过可修改的左值完成此写访问可能很重要。 上面的有效类型规则没有提到限定符,“严格别名规则”本身并不关心对象是否被限定(“类型”可以别名“限定类型”,反之亦然)。

但是在其他情况下,有效类型是否为只读可能很重要:最值得注意的是,如果我们稍后尝试对有效类型为 const 限定的对象进行非限定左值访问。 (C11 6.7.3/6 “如果试图通过使用具有非 const 限定类型的左值来修改以 const 限定类型定义的对象,则行为未定义。”)来自上面先前引用的部分左值,有效类型具有限定符是有意义的,即使标准没有明确提及。

因此,为了绝对确定,我们必须获得用于左值访问权限的类型。 如果整个对象是只读的,那么应该使用这篇文章顶部的第二个片段。 否则,如果它是读/写(但可能具有合格成员),则应使用第一个片段。 那么无论您如何阅读标准,它都不会出错。

有趣的是,我发现这种 C99 方式在 clang 中有效,但在 gcc 中无效

int main(int argc, const char *argv[])
{
    struct deneme *pmydeneme = malloc(sizeof(struct deneme));
    *pmydeneme = (struct deneme) {15, 20};
    return 0;
}

我不同意Christ Dodd的回答,因为我认为他的解决方案根据标准给出了Undefined Behavior ,正如其他人所说。

为了以不调用未定义行为的方式“解决” const限定符,我提出以下解决方案:

  1. 定义一个用malloc()调用初始化的void*变量。
  2. 定义所需类型的对象,在本例中为struct deneme并以const限定符不会抱怨的某种方式对其进行初始化(即,在声明行本身中)。
  3. 使用memcpy()struct deneme对象的位复制到void*对象。
  4. 声明一个指向struct deneme对象的指针并将其初始化为(void*)变量,之前已转换为(struct deneme *)

所以,我的代码是:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
struct deneme {
    const int a;
    const int b;
};
struct deneme* deneme_init(struct deneme data) {
    void *x = malloc(sizeof(struct deneme));
    memcpy(x, &data, sizeof(struct deneme));
    return (struct deneme*) x;
}
int main(void) {
    struct deneme *obj = deneme_init((struct deneme) { 15, 20, } );
    printf("obj->a: %d, obj->b: %d.\n", obj->a, obj->b);
    return 0;
}

标准使用const关键字作为左值限定符和存储类之间的奇怪混合,但没有明确哪个含义适用于结构成员。

如果一个结构体s类型为struct S ,成员mT类型,则构造s.foo接受一个struct S类型的左值并从中派生一个T类型的左值。 如果T包含限定符,则该修饰符将影响由此产生的左值。

标准当然承认代码可能采用非const限定的左值,从该左值派生出一个具有const限定的左值,从那个 - 就像原始 - 不是的左值派生,然后使用后者左值来修改对象。 不清楚的是,在结构成员上使用const修饰符是否会影响对象的底层存储类,或者它是否只会导致将const修饰符应用于使用成员访问运算符形成的任何左值。 我认为后一种解释更有意义,因为前者会导致许多模棱两可且不可行的极端情况,但我认为该标准没有明确说明应该适用哪种解释。 由于在前一种解释下定义其行为的所有情况在后一种解释下都将被相同定义,我认为标准的作者没有理由不认为后一种解释更优越,但他们可能希望留下这种可能性在某些情况下,在某些实施中,前一种解释可能会提供委员会没有预见到的一些有利实施。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM