为什么我的初始化常数在一个版本中有效,而在另一个版本中无效?

huangapple go评论70阅读模式
英文:

Why is my initializer constant in one version, but not valid another version?

问题

我正在尝试声明一个结构体的静态数组,当它在全局范围内声明时可以这样做,但在函数内部声明为静态时就不行了。

以下是可以正常工作的一些示例代码:

#include <stdio.h>

enum
{
    Mammals,
    Amphibians,
    Avians,
    Fish,
    Human, Elephant, Horse,
    Frog, Salamander,
    Eagle, Sparrow, Duck,
    Salmon, Carp, Tuna
};

typedef struct
{
    int category;
    int* examples;
    int num_examples;
} classification_t;

static classification_t classifications[] = {
    { Mammals,    (int[]){Human, Elephant, Horse}, 3 },
    { Amphibians, (int[]){Frog, Salamander},       2 },
    { Avians,     (int[]){Eagle, Sparrow, Duck},   3 },
    { Fish,       (int[]){Salmon, Carp, Tuna},     3 }
};

int main(void)
{
    for(int i=0; i<4; ++i)
    {
        printf("Category %d has %d examples\n", classifications[i].category, classifications[i].num_examples);
    }
    
    return 0;
}

输出结果:

Category 0 has 3 examples
Category 1 has 2 examples
Category 2 has 3 examples
Category 3 has 3 examples

这段代码之所以有效,是因为classifications是一个静态全局变量。

然而,当我尝试将表格移动到main函数内部(或任何其他函数内部)时,我会收到一个错误:

prog.c:25:17: error: initializer element is not constant { Mammals,    (int[]){Human, Elephant, Horse}, 3 },

在这段代码中:

#include <stdio.h>

enum
{
    Mammals,
    Amphibians,
    Avians,
    Fish,
    Human,  Elephant,    Horse,
    Frog,   Salamander,
    Eagle,  Sparrow,     Duck,
    Salmon, Carp,        Tuna
};

typedef struct
{
    int category;
    int* examples;
    int num_examples;
} classification_t;

int main(void)
{
    static classification_t classifications[] = {
        { Mammals,    (int[]){Human, Elephant, Horse}, 3 },
        { Amphibians, (int[]){Frog, Salamander},       2 },
        { Avians,     (int[]){Eagle, Sparrow, Duck},   3 },
        { Fish,       (int[]){Salmon, Carp, Tuna},     3 }
    };

    for(int i=0; i<4; ++i)
    {
        printf("Category %d has %d examples\n", classifications[i].category, classifications[i].num_examples);
    }

    return 0;
}

对我来说完全不清楚为什么在第一个示例中数组初始化有效,但在第二个示例中不起作用。

是否有一种简单的方法可以使这个大表格是静态的,但在函数范围内声明?

正如一位评论者指出的,整个问题可以更直接地描述如下:

static int* foo = (int[]) { 3, 4, 5 };
// 这是有效的;显然,匿名数组是一个常量指针

int main()
{
  static int* foo = (int[]) { 3, 4, 5 };
  // 这是无效的;匿名数组可能在栈上

  static int* foo = (static int[]) { 3, 4, 5 };
  // 这是无效的;在int[]前面使用static关键字是无效的
  // 但这是一种尝试使数组“静态”的方法
}

有没有办法在函数内部声明一个静态的匿名数组?

英文:

I'm trying to declare a static array of structures and can do so when it is declared globally, but not when declared static within a function.

Here's some sample code that works properly:

#include &lt;stdio.h&gt;

enum
{
	Mammals,
	Amphibians,
	Avians,
	Fish,
	Human, 	Elephant,	Horse,
	Frog,	Salamander,
	Eagle, Sparrow, Duck,
	Salmon, Carp, Tuna
};

typedef struct
{
	int category;
	int* examples;
	int num_examples;
} classification_t;

static classification_t classifications[] = {
	{ Mammals,    (int[]){Human, Elephant, Horse}, 3 },
	{ Amphibians, (int[]){Frog, Salamander},       2 },
	{ Avians,     (int[]){Eagle, Sparrow, Duck},   3 },
	{ Fish,       (int[]){Salmon, Carp, Tuna},     3 }
};

int main(void)
{
	for(int i=0; i&lt;4; ++i)
	{
		printf(&quot;Category %d has %d examples\n&quot;, classifications[i].category, classifications[i].num_examples);
	}
	
	return 0;
}

Output:

Category 0 has 3 examples
Category 1 has 2 examples
Category 2 has 3 examples
Category 3 has 3 examples

This works because the big table of classifications is a static-global variable.

However, when I try to move the table within main (or any other function), I get an error:

prog.c:25:17: error: initializer element is not constant { Mammals,    (int[]){Human, Elephant, Horse}, 3 },

In this code:

#include &lt;stdio.h&gt;

enum
{
	Mammals,
	Amphibians,
	Avians,
	Fish,
	Human, 	Elephant,	Horse,
	Frog,	Salamander,
	Eagle, Sparrow, Duck,
	Salmon, Carp, Tuna
};

typedef struct
{
	int category;
	int* examples;
	int num_examples;
} classification_t;

int main(void)
{
    //
    // The ONLY change is to move the table from outside main to inside main.
    // error: initializer element is not constant { Mammals,    (int[]){Human, Elephant, Horse}, 3 },
    //
	static classification_t classifications[] = {
		{ Mammals,    (int[]){Human, Elephant, Horse}, 3 },
		{ Amphibians, (int[]){Frog, Salamander},       2 },
		{ Avians,     (int[]){Eagle, Sparrow, Duck},   3 },
		{ Fish,       (int[]){Salmon, Carp, Tuna},     3 }
	};

	for(int i=0; i&lt;4; ++i)
	{
		printf(&quot;Category %d has %d examples\n&quot;, classifications[i].category, classifications[i].num_examples);
	}
	
	return 0;
}

It is not at all clear to me why the array initialization works in the first example, but not the second one.

Is there a simple technique to make this big table static, but declared within a function scope?

<hr>
<hr>
As one commenter pointed out, the entire problem can be described more directly like this:

static int* foo = (int[]) { 3, 4, 5 };
// This is Valid; apparently, the anonymous array is a constant-pointer

int main()
{
  static int* foo = (int[]) { 3, 4, 5 };
  // This is NOT valid; the anonymous array is likely on the stack.

  static int* foo = (static int[]) { 3, 4, 5 };
  // This is NOT valid; the static keyword is invalid before int[]
  // But it was an attempt to make the array &quot;static&quot;
}

Is there some way to declare a static, anonymous array within a function?

答案1

得分: 6

以下是翻译好的部分:

这里的区别在于作为静态对象初始化器使用的复合字面量的存储期。

当您在文件作用域中使用它们时,它们具有静态存储期,并且可以用作常量表达式来初始化对象,

当您在主函数内部使用它们(在块作用域中)时,它们具有自动存储期,也就是说不能用作常量表达式来初始化具有静态存储期的对象。

来自C标准)6.6 常量表达式)

  1. 常量表达式可以在翻译期间而非运行时进行评估,因此可以在可以使用常量的任何地方使用它。

7 初始化器中对常量表达式有更大的宽容度。这样的常量表达式应为以下之一,或者求值为以下之一:

//...

地址常量,或

//...

9 地址常量是空指针,指向具有静态存储期的对象的左值的指针,或者指向函数指示符的指针;必须明确使用一元操作符“&”或将整数常量转换为指针类型来创建它,或者通过使用数组或函数类型的表达式隐式创建。在创建地址常量时可以使用数组下标 [] 和成员访问 . 和 -> 操作符,地址 & 和间接 * 一元操作符以及指针转换,但是不得使用这些操作符访问对象的值。

请注意,作为数组初始化器的复合字面量具有数组类型,例如

(int[]){Human, Elephant, Horse}

会隐式转换为指向它们的第一个元素的指针。

英文:

The difference is the storage duration of the compound literals used as initializers of static objects.

When you are using them in the file scope they have static storage duration and may be used as constant expressions to initialize objects,

When you are using them within main (in block scope) then they have automatic storage duration that is they may not be used as constant expressions to initialize objects with static storage duration.

From the C Standard )6.6 Constant expressions)

> 2 A constant expression can be evaluated during translation rather
> than runtime, and accordingly may be used in any place that a constant
> may be.
>
> 7 More latitude is permitted for constant expressions in initializers.
> Such a constant expression shall be, or evaluate to, one of the
> following:
>
> //...
>
> — an address constant, or
>
> //...

and

> 9 An address constant is a null pointer, a pointer to an lvalue
> designating an object of static storage duration
, or a pointer to a
> function designator; it shall be created explicitly using the unary &
> operator or an integer constant cast to pointer type, or implicitly by
> the use of an expression of array or function type
. The
> array-subscript [] and member-access . and -> operators, the address &
> and indirection * unary operators, and pointer casts may be used in
> the creation of an address constant, but the value of an object shall
> not be accessed by use of these operators.

Pay attention to that the compound literals used as initializers of your array and having array types as for example

(int[]){Human, Elephant, Horse}

are implicitly converted to pointers to their first elements.

答案2

得分: 5

你在函数中声明时遇到错误,因为初始化程序在编译时不是已知的。

来自C标准第6.5.2.5节关于复合文字的说明:

复合文字的值是由初始化程序列表初始化的无名对象的值。如果复合文字出现在函数体外部,则对象具有静态存储期;否则,它具有与封闭块相关联的自动存储期。

因此,当您在文件范围内声明对象时,复合文字具有静态存储期,因此它们的地址在编译时是已知的。但是当您在函数内部声明一个static对象时,复合文字具有自动存储期,因此不能用来初始化具有静态存储期的对象。

英文:

You're getting the error with the declaration in a function because the initializer is not known at compile time.

From section 6.5.2.5 of the C standard regarding compound literals:

> The value of the compound literal is that of an unnamed object
> initialized by the initializer list. If the compound literal occurs
> outside the body of a function, the object has static storage
> duration; otherwise, it has automatic storage duration associated with
> the enclosing block.

So when your object is declared at file scope, the compound literals have static storage duration, and therefore their addresses are known at compile time. But when you declare a static object inside of a function, the compound literals have automatic storage duration, and therefore cannot be used to initialize an object with static storage duration.

答案3

得分: 2

以下是您要翻译的部分:

Why is my initializer constant in one version, but not valid another version?
为什么我的初始化常量在一个版本中有效,而在另一个版本中无效?

Two other answers already explain this, so I'll just summarize: compound literals appearing at block scope have automatic storage duration, so their addresses are not address constants, whereas compound literals appearing at file scope have static storage duration, and their addresses are address constants. This analogous to the storage duration of named objects declared in those scopes.
其他两个答案已经解释了这一点,所以我只是简要总结一下:出现在块作用域的复合字面值具有自动存储期,因此它们的地址不是地址常量,而出现在文件作用域的复合字面值具有静态存储期,它们的地址是地址常量。这类似于在这些作用域中声明的具有命名对象的存储期。

Is there a simple technique to make this big table static, but declared within a function-scope?
有没有一种简单的方法可以使这个大表格是静态的,但在函数作用域内声明的?

You could declare the inner arrays as named static objects inside the function, and use those to initialize the big table.
你可以在函数内将内部数组声明为命名的静态对象,并使用它们来初始化大表格。

You could perhaps make classification_t.examples an array (of sufficient length) instead of a pointer, so that you don't need to rely on compound literals to initialize it in the first place.
你也可以将classification_t.examples可能变成一个数组(足够长),而不是一个指针,这样你就不需要依赖复合字面值来初始化它了。

But ...
但是...

Is there some way to declare a static, anonymous array within a function?
有没有一种在函数内声明静态的匿名数组的方法?

No.
没有。

However, frame challenge: do you really need an array with static storage duration? In your example, you are declaring the array in main, so unless you re-enter main or the array is extremely large, there is little practical difference between a static array and an automatic one. And if the array is automatic, then the initializer is not limited to constant expressions:
然而,挑战现状:你真的需要一个具有静态存储期的数组吗?在你的示例中,你是在main中声明数组,所以除非重新进入main或数组非常大,否则静态数组和自动数组之间几乎没有实际差异。如果数组是自动的,那么初始化器不受常量表达式的限制:

    // not static:
    classification_t classifications[] = {
        { Mammals, (int[]){Human, Elephant, Horse}, 3 },
        { Amphibians, (int[]){Frog, Salamander}, 2 },
        { Avians, (int[]){Eagle, Sparrow, Duck}, 3 },
        { Fish, (int[]){Salmon, Carp, Tuna}, 3 }
    };
}```

Alternatively, the primary semantic difference between declaring your array at file scope and declaring it at block scope is linkage. Even when you declare it at file scope you are giving it internal linkage. Is that really not good enough? Is it essential that it have no linkage?
或者,将数组声明在文件作用域和块作用域之间的主要语义差异是链接性。即使在文件作用域中声明它,你也是在给它内部链接性。这真的不够好吗?它是否必须没有链接性?

<details>
<summary>英文:</summary>

&gt; Why is my initializer constant in one version, but not valid another version?

Two other answers already explain this, so I&#39;ll just summarize: compound literals appearing at block scope have automatic storage duration, so their addresses are not address constants, whereas compound literals appearing at file scope have static storage duration, and *their* addresses *are* address constants.  This analogous to the storage duration of named objects declared in those scopes.

&gt; Is there a simple technique to make this big table static, but declared within a function-scope?

You could declare the inner arrays as *named* `static` objects inside the function, and use those to initialize the big table.

You could perhaps make `classification_t.examples` an array (of sufficient length) instead of a pointer, so that you don&#39;t need to rely on compound literals to initialize it in the first place.

But ...

&gt; Is there some way to declare a static, anonymous array within a function?

No.

However, **frame challenge**: do you really need an array with static storage duration?  In your example, you are declaring the array in `main`, so unless you re-enter `main` or the array is extremely large, there is little practical difference between a static array and an automatic one.  And if the array is automatic, then the initializer is not limited to constant expressions:

int main(void) {
// not static:
classification_t classifications[] = {
{ Mammals, (int[]){Human, Elephant, Horse}, 3 },
{ Amphibians, (int[]){Frog, Salamander}, 2 },
{ Avians, (int[]){Eagle, Sparrow, Duck}, 3 },
{ Fish, (int[]){Salmon, Carp, Tuna}, 3 }
};
}

Alternatively, the primary semantic difference between declaring your array at file scope and declaring it at block scope is linkage.  Even when you declare it at file scope you are giving it internal linkage.  Is that really not good enough?  Is it essential that it have *no* linkage?

</details>



huangapple
  • 本文由 发表于 2023年2月9日 02:11:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/75390058.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定