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

时间:2012-03-13 20:16:05

标签: c struct initialization malloc constants

我试过了;

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分配内存时,有没有办法在struct中初始化const变量?

6 个答案:

答案 0 :(得分:23)

你需要抛弃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静态和/或全局(所以它只需要构建一次)。


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

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

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

(但是,可以通过读取malloc分配的空格来违反严格别名规则。

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

答案 1 :(得分:10)

你有没有试过这样做:

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;
}

我没有测试,但代码似乎正确

答案 2 :(得分:2)

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

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

  1. 定义使用void*调用初始化的malloc()变量。
  2. 定义所需类型的对象,在本例中为struct deneme,并以const限定符不抱怨的方式(即在声明行本身中)初始化它。
  3. 使用memcpy()struct deneme对象的位复制到void*对象。
  4. 声明指向struct deneme对象的指针,并将其初始化为先前转换为(void*)的{​​{1}}变量。
  5. 所以,我的代码是:

    (struct deneme *)

答案 3 :(得分:1)

有趣的是我发现这种C99方式适用于clang而不是gcc

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

答案 4 :(得分:1)

要扩展@Chris Dodd的回答,我已经阅读了该标准的“语言律师”详细信息,并且看来这段代码定义明确:

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

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

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; 

理论上:

首先要确定的是,如果所谓的 lvalue 具有类型,如果是,则该类型是否带有限定符。这是在C11 6.3.2.1/1中定义的:

lvalue 是一个表达式(对象类型不是void),可能 指定一个对象;如果左值在评估时未指定对象,则该行为未定义。当说一个对象具有特定类型时,该类型由用于指定该对象的左值指定。 可修改的左值是不具有数组类型,不完整的类型,不具有constqualified类型的左值,并且如果是结构或联合,则不具有任何成员(包括(递归地,所有包含的聚合或并集的任何成员或元素)都具有限定类型。

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

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

用于访问其存储值的对象的有效类型是该对象的声明的类型(如果有)。 87)如果将值存储到通过一个没有声明类型的对象 具有非字符类型的左值,则左值的类型变为 该访问以及未修改的后续访问的对象的有效类型 储值。如果将值复制到没有声明类型的对象中,则使用 memcpymemmove,或者被复制为字符类型数组,然后是有效类型 修改后的对象的访问权限以及不修改 value是从中复制值的对象的有效类型(如果有)。对于 对没有声明类型的对象的所有其他访问,则该对象的有效类型为 只是用于访问的左值的类型。

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

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

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

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

但是在其他情况下,有效类型是否为只读也可能很重要:最显着的是,如果我们以后尝试对有效类型为const限定的对象进行非限定的左值访问。 (C11 6.7.3 / 6“如果试图通过使用具有非const限定类型的左值来修改由const限定类型定义的对象,则该行为是不确定的。”)左值,即使标准没有明确提及,有效类型具有限定符也是有道理的。

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

答案 5 :(得分:0)

该标准使用const关键字作为左值限定符和存储类之间的怪异混合体,但是并不清楚哪种含义适用于结构成员。

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

该标准肯定会认识到代码可能采用不符合T的左值的可能性,是从符合T的左值的情况下得出的,例如原始-不是,然后使用后一个左值修改对象。尚不清楚的是,是否在结构成员上使用const修饰符会影响对象的基础存储类,还是仅会导致const修饰符应用于使用成员访问运算符。我认为后者的解释更有意义,因为前者会导致许多模棱两可且无法解决的极端情况,但我认为标准并未明确指出应采用哪种解释。由于在前一种解释下将定义其行为的所有情况都将在后一种解释下进行相同的定义,因此,我认为标准的作者没有理由不会认为后一种解释是优越的,但他们可能希望保留这种可能性。在某些情况下,在某些实施方式上,前一种解释可能会提供委员会未曾预见到的某些优势实施方式。