如何在C中初始化结构中的常量(使用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变量吗?

您需要丢弃常量来初始化malloc’ed结构的字段:

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

或者,您可以创建结构的初始化版本并记住它:

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,因为CCD_;用const限定类型"定义的对象"strong>";。表达式mydeneme->a不是对象,它是一个表达式。尽管它具有const限定类型,但它表示一个没有用const限定类型定义的对象(事实上,根本没有用任何类型定义)。

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

(然而,从malloc分配的空间中读取可能会违反严格的混叠规则)。

在Chris的代码样本中,第一个将整数值的有效类型设置为int,第二个将有效类型设置成const int,然而在这两种情况下,继续通过CCD_ 9读取这些值是正确的,因为严格的混叠规则(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;
}

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

有趣的是,我发现这种C99方式在clang中工作,但在gcc 中不工作

int main(int argc, const char *argv[])
{
    struct deneme *pmydeneme = malloc(sizeof(struct deneme));
    *pmydeneme = (struct deneme) {15, 20};
    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),它可能指定对象;如果一个左值在求值时没有指定对象,则该行为是未定义的。当一个对象被称为具有特定类型时,该类型由用于指定该对象的左值指定。可修改的左值是一个没有数组类型、没有不完整类型、没有constqualified类型的左值,如果它是一个结构或并集,则没有任何具有constqualified类型的成员(递归地,包括所有包含的聚合或并集的任何成员或元素)。

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

继续讨论";"严格混叠";和有效类型,C11 6.5/7:

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

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

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

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

这就是它变得模糊的地方:写访问是否通过可修改的左值完成可能很重要。上面的有效类型规则没有提到限定符和";严格混叠规则";这样就不关心对象是否合格("类型"可以别名为"<合格类型>",反之亦然)。

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

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

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

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

  1. 定义一个用malloc0调用初始化的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关键字作为左值限定符和存储类之间的奇怪混合,但没有明确哪种含义适用于结构成员。

如果具有struct S类型的结构sT类型的成员m,则构造s.foostruct S类型的左值,并从中导出T类型的左值。如果T包含限定符,则该修饰符将影响由此产生的左值。

该标准当然认识到,代码可能会采用一个不符合const条件的左值,从该左值派生出一个符合const条件的左值,从与原始左值一样不符合条件的左值派生出,然后使用后一个左值来修改对象。尚不清楚的是,在结构成员上使用const修饰符是否会影响对象的底层存储类,或者它是否只会导致const修饰符应用于使用成员访问运算符形成的任何左值。我认为后一种解释更有意义,因为前者导致了许多模棱两可和不可行的角落案例,但我不认为《标准》明确了应该适用哪种解释。由于在前一种解释下对其行为进行定义的所有情况在后一种解释中都会有相同的定义,我认为标准的作者没有理由不认为后一种理解是优越的,但他们可能想保留这样一种可能性,即在某些情况下,在某些实现中,前一种解释可能会提供一些委员会没有预见到的优势。

相关内容

  • 没有找到相关文章

最新更新