简体   繁体   English

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

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

I have tried;我试过了;

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

And this is the compiler's error:这是编译器的错误:

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

And, also this;而且,还有这个;

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

And this is the compiler's error:这是编译器的错误:

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

And neither got compiled.两者都没有被编译。 Is there any way to initialize a const variable inside a struct when allocation memory with malloc?使用 malloc 分配内存时,有什么方法可以在结构内初始化 const 变量?

You need to cast away the const to initialize the fields of a malloc'ed structure:您需要抛弃 const 来初始化 malloc 结构的字段:

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

Alternately, you can create an initialized version of the struct and memcpy it:或者,您可以创建结构的初始化版本并对其进行 memcpy:

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

You can make deneme_init static and/or global if you do this a lot (so it only needs to be built once).如果您deneme_init这样做,您可以将deneme_init静态和/或全局(因此它只需要构建一次)。


Explanation of why this code is not undefined behaviour as suggested by some of the comments, using C11 standard references:使用 C11 标准参考解释为什么此代码不是某些注释所建议的未定义行为:

  • This code does not violate 6.7.3/6 because the space returned by malloc is not "an object defined with a const-qualified type".此代码不违反 6.7.3/6,因为malloc返回的空间不是“使用 const 限定类型定义的对象”。 The expression mydeneme->a is not an object, it is an expression.表达式mydeneme->a不是一个对象,它是一个表达式。 Although it has const -qualified type, it denotes an object which was not defined with a const-qualified type (in fact, not defined with any type at all).虽然它有const限定类型,但它表示一个没有用 const 限定类型定义的对象(实际上,根本没有用任何类型定义)。

  • The strict aliasing rule is never violated by writing into space allocated by malloc , because the effective type (6.5/6) is updated by each write.写入malloc分配的空间永远不会违反严格的别名规则,因为每次写入都会更新有效类型(6.5/6)。

(The strict aliasing rule can be violated by reading from space allocated by malloc however). (但是,从malloc分配的空间中读取可能会违反严格的别名规则)。

In Chris's code samples, the first one sets the effective type of the integer values to int , and the second one sets the effective type to const int , however in both cases going on to read those values through *mydeneme is correct because the strict-aliasing rule (6.5/7 bullet 2) permits reading an object through an expression which is equally or more qualified than the effective type of the object.在 Chris 的代码示例中,第一个将整数值的有效类型设置为int ,第二个将有效类型设置为const int ,但是在这两种情况下通过*mydeneme读取这些值都是正确的,因为*mydeneme别名规则(6.5/7 项目符号 2)允许通过与对象的有效类型相同或更多限定的表达式来读取对象。 Since the expression mydeneme->a has type const int , it can be used to read objects of effective type int and const int .由于表达式mydeneme->a类型为const int ,因此它可用于读取有效类型intconst int

Have you tried to do like this:你有没有试过这样做:

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

I have not tested but the code seems correct我还没有测试过,但代码似乎是正确的

To expand on the answer by @Chris Dodd, I've been reading through the "language-lawyer" details of the standard and it appears that this code is well-defined:为了扩展@Chris Dodd 的答案,我一直在阅读标准的“语言律师”详细信息,看来这段代码定义明确:

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

Alternatively, to dynamically create a complete struct object that's const qualified:或者,要动态创建一个完整的 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; 

Rationale:理由:

The first thing one needs to establish when getting to the bottom of this, is if a so-called lvalue has a type and if so, does that type come with qualifiers or not.在深入了解这一点时,首先需要确定的是,所谓的左值是否具有类型,如果有,该类型是否带有限定符。 This is defined in C11 6.3.2.1/1:这在 C11 6.3.2.1/1 中定义:

An lvalue is an expression (with an object type other than void) that potentially designates an object;左值是可能指定对象的表达式(具有除 void 之外的对象类型); if an lvalue does not designate an object when it is evaluated, the behavior is undefined.如果左值在评估时未指定对象,则行为未定义。 When an object is said to have a particular type, the type is specified by the lvalue used to designate the object.当一个对象被称为具有特定类型时,该类型由用于指定该对象的左值指定。 A modifiable lvalue is an lvalue that does not have array type, does not have an incomplete type, does not have a constqualified type, and if it is a structure or union, does not have any member (including, recursively, any member or element of all contained aggregates or unions) with a constqualified type.可修改左值是没有数组类型、没有不完整类型、没有常量限定类型的左值,如果它是结构或联合,则没有任何成员(递归地包括任何成员或元素)所有包含的聚合或联合)具有 const 限定类型。

So clearly a lvalue does not only have a type, but also qualifiers.很明显,左值不仅有类型,还有限定符。 It is not a modifiable lvalue if it is const-qualified or if it is a struct with const-qualified members.如果它是 const 限定的或者如果它是具有 const 限定成员的结构,则它不是可修改的左值。

Moving on to the rules of "strict aliasing" and effective type , C11 6.5/7:继续讨论“严格别名”和有效类型的规则,C11 6.5/7:

The effective type of an object for an access to its stored value is the declared type of the object, if any.访问其存储值的对象的有效类型是该对象的声明类型(如果有)。 87) If a value is stored into an object having no declared type through an lvalue having a type that is not a character type, then the type of the lvalue becomes the effective type of the object for that access and for subsequent accesses that do not modify the stored value. 87)如果一个值通过一个类型不是字符类型的左值存储到一个没有声明类型的对象中,那么左值的类型成为该访问和后续访问的有效类型修改存储的值。 If a value is copied into an object having no declared type using memcpy or memmove , or is copied as an array of character type, then the effective type of the modified object for that access and for subsequent accesses that do not modify the value is the effective type of the object from which the value is copied, if it has one.如果使用memcpymemmove将值复制到没有声明类型的对象中,或者复制为字符类型的数组,则该访问和不修改该值的后续访问的修改对象的有效类型是从中复制值的对象的有效类型(如果有)。 For all other accesses to an object having no declared type, the effective type of the object is simply the type of the lvalue used for the access.对于没有声明类型的对象的所有其他访问,对象的有效类型只是用于访问的左值的类型。

  1. Allocated objects have no declared type.分配的对象没有声明类型。

This means that the allocated chunk returned by malloc has no effective type until something is stored inside that memory location through a lvalue write access, either through assignment or memcpy .这意味着 malloc 返回的分配块没有有效类型,直到通过左值写入访问(通过赋值或memcpy某些内容存储在该内存位置中。 It then gets the effective type of the lvalue used in that write access.然后它获取该写访问中使用的左值的有效类型。

Notably, the type of the pointer pointing at that memory location is completely irrelevant.值得注意的是,指向该内存位置的指针的类型是完全无关的。 It might as well have been a volatile bananas_t* since it isn't used to access the lvalue (at least not yet).它也可能是一个volatile bananas_t*因为它不用于访问左值(至少现在还没有)。 Only the type used for lvalue access matters.只有用于左值访问的类型才重要。

Now this is where it gets fuzzy: it might matter if this write access is done through a modifiable lvalue or not.现在这就是它变得模糊的地方:是否通过可修改的左值完成此写访问可能很重要。 The rule of effective type above doesn't mention qualifiers and the "strict aliasing rule" as such doesn't care about if an object is qualified or not (" type " may alias " qualified type " and vice versa).上面的有效类型规则没有提到限定符,“严格别名规则”本身并不关心对象是否被限定(“类型”可以别名“限定类型”,反之亦然)。

But there are other cases where it might matter if the effective type is read-only or not: most notably if we later attempt to do a non-qualified lvalue access to an object which effective type is const-qualified.但是在其他情况下,有效类型是否为只读可能很重要:最值得注意的是,如果我们稍后尝试对有效类型为 const 限定的对象进行非限定左值访问。 (C11 6.7.3/6 "If an attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type, the behavior is undefined.") From the previously quoted part above lvalues, it makes sense for effective type to have a qualifier, even if the standard doesn't mention that explicitly. (C11 6.7.3/6 “如果试图通过使用具有非 const 限定类型的左值来修改以 const 限定类型定义的对象,则行为未定义。”)来自上面先前引用的部分左值,有效类型具有限定符是有意义的,即使标准没有明确提及。

Therefore to be absolutely sure, we have to get the type used for the lvalue access right.因此,为了绝对确定,我们必须获得用于左值访问权限的类型。 If the whole object is meant to be read-only, then the second snippet on top of this post should be used.如果整个对象是只读的,那么应该使用这篇文章顶部的第二个片段。 Otherwise, if it is read/write (but potentially with qualified members), the first snippet should be used.否则,如果它是读/写(但可能具有合格成员),则应使用第一个片段。 Then it can never go wrong no matter how you read the standard.那么无论您如何阅读标准,它都不会出错。

Interesting I found this C99 way is working in clang but not in gcc有趣的是,我发现这种 C99 方式在 clang 中有效,但在 gcc 中无效

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

I disagree with Christ Dodd 's answer, since I think his solution gives Undefined Behaviour according to the standards, as others said.我不同意Christ Dodd的回答,因为我认为他的解决方案根据标准给出了Undefined Behavior ,正如其他人所说。

To "work-around" the const qualifier in a way that does not invoke undefined behaviour, I propose the following solution:为了以不调用未定义行为的方式“解决” const限定符,我提出以下解决方案:

  1. Define a void* variable initialized with a malloc() call.定义一个用malloc()调用初始化的void*变量。
  2. Define and object of the desired type, in this case struct deneme and initialize it in some way that const qualifier does not complain (that is, in the declaration-line itself).定义所需类型的对象,在本例中为struct deneme并以const限定符不会抱怨的某种方式对其进行初始化(即,在声明行本身中)。
  3. Use memcpy() to copy the bits of the struct deneme object to the void* object.使用memcpy()struct deneme对象的位复制到void*对象。
  4. Declare a pointer to struct deneme object and initialize it to the (void*) variable, previously cast to (struct deneme *) .声明一个指向struct deneme对象的指针并将其初始化为(void*)变量,之前已转换为(struct deneme *)

So, my code would be:所以,我的代码是:

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

The Standard uses the const keyword as a weird hybrid between an lvalue qualifier and a storage class, but doesn't make clear which meaning is applicable to struct members.标准使用const关键字作为左值限定符和存储类之间的奇怪混合,但没有明确哪个含义适用于结构成员。

If one has a structure s of type struct S with member m of type T , the construct s.foo takes an lvalue of type struct S and derives from it an lvalue of type T .如果一个结构体s类型为struct S ,成员mT类型,则构造s.foo接受一个struct S类型的左值并从中派生一个T类型的左值。 If T contains a qualifier, that modifier will affect the lvalue thus produced.如果T包含限定符,则该修饰符将影响由此产生的左值。

The Standard certainly recognizes the possibility that code may take an lvalue which is not const -qualified, derive from that an lvalue that is const -qualified, derive from that one which--like the original--is not, and then use the latter lvalue to modify the object.标准当然承认代码可能采用非const限定的左值,从该左值派生出一个具有const限定的左值,从那个 - 就像原始 - 不是的左值派生,然后使用后者左值来修改对象。 What is unclear is whether the use of a const modifier on a struct member affects the underlying storage class of an object, or whether it would merely cause a const modifier to be applied to any lvalue formed using the member-access operator.不清楚的是,在结构成员上使用const修饰符是否会影响对象的底层存储类,或者它是否只会导致将const修饰符应用于使用成员访问运算符形成的任何左值。 I think the latter interpretation makes a lot more sense, since the former leads to many ambiguous and unworkable corner cases, but I don't think the Standard makes clear which interpretation is supposed to apply.我认为后一种解释更有意义,因为前者会导致许多模棱两可且不可行的极端情况,但我认为该标准没有明确说明应该适用哪种解释。 Since all situations whose behavior would be defined under the former interpretation would be defined identically under the latter, I see no reason the authors of the Standard wouldn't have regarded the latter interpretation as superior, but they might have wanted to leave open the possibility that in some cases, on some implementations, the former interpretation might offer some advantage implementation the Committee hadn't foreseen.由于在前一种解释下定义其行为的所有情况在后一种解释下都将被相同定义,我认为标准的作者没有理由不认为后一种解释更优越,但他们可能希望留下这种可能性在某些情况下,在某些实施中,前一种解释可能会提供委员会没有预见到的一些有利实施。

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

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