C++ 中使用什么类型来定义数组大小?
What type is used in C++ to define an array size?
为 8 位微控制器编译 avr-gcc
中的一些测试代码,行
const uint32_t N = 65537;
uint8_t values[N];
我收到以下编译警告(默认情况下应该是一个错误,真的)
warning: conversion from 'long unsigned int' to 'unsigned int' changes value from '65537' to '1' [-Woverflow]
uint8_t values[N];
请注意,为此目标编译时,sizeof(int)
为 2。
看来,数组的大小不能超过unsigned int
的大小。
我说的对吗?这是特定于 GCC 的还是某些 C 或 C++ 标准的一部分?
在有人评论说 8 位微控制器通常没有足够的内存来容纳这么大的数组之前,我只想说这不是重点。
size_t
被认为是要使用的类型,尽管没有被 C 或 C++ 标准正式批准。
这样做的理由是 sizeof(values)
将是那种类型( 是 由 C 和 C++ 标准规定),并且元素的数量将是不一定大于此值,因为对象的 sizeof
至少为 1.
sizeof
返回的值将是 size_t
类型。
它通常用作数组中元素的数量,因为它足够大。 size_t
始终是无符号的,但它是实现定义的类型。最后,它是由实现定义的,实现是否可以支持甚至 SIZE_MAX
字节的对象......甚至接近它。
在您的实现中,size_t
定义为 unsigned int
,uint32_t
定义为 long unsigned int
。创建 C 数组时,数组大小的参数会被编译器隐式转换为 size_t
。
这就是您收到警告的原因。您使用 uint32_t
指定数组大小参数,该参数被转换为 size_t
并且这些类型不匹配。
这可能不是您想要的。请改用 size_t
。
So it seems that, at an array size cannot exceed the size of an
unsigned int
.
在您的特定 C[++] 实现中似乎是这种情况。
Am I correct? Is this gcc-specific or is it part of some C or C++
standard?
它通常不是 GCC 的特性,也不是 C 或 C++ 标准指定的。它是您的特定 实现的特征:适用于您的特定计算平台的 GCC 版本。
C 标准要求指定数组元素数的表达式具有整数类型,但并未指定特定类型。我确实认为您的 GCC 似乎声称它为您提供了一个包含与您指定的元素数量不同的数组,这很奇怪。我认为这不符合标准,而且我认为作为扩展没有多大意义。我宁愿看到它拒绝代码。
我将用 "incorrekt and incomplet" ISO CPP 标准草案 n4659 中的规则来剖析这个问题。重点是我加的。
11.3.4 定义数组声明。第一段包含
If the constant-expression [between the square brackets] (8.20) is present, it shall be a converted constant expression of type std::size_t [...].
std::size_t
来自 <cstddef>
并定义为
[...] an implementation-defined unsigned integer type that is large enough to contain the size in bytes of any object.
由于它是通过 C 标准库头文件导入的,因此 C 标准与 size_t
的属性相关。 ISO C 草案 N2176 在 7.20.3 中规定了整数类型的 "minimal maximums",如果需要的话。对于 size_t
,最大值为 65535。换句话说,16 位 size_t
完全符合。
一个"converted constant expression"定义在8.20/4:
A converted constant expression of type T is an expression, implicitly converted to type T, where the converted expression is a constant expression and the implicit conversion sequence contains only [any of 10 distinct conversions, one of which concerns integers (par. 4.7):]
— integral conversions (7.8) other than narrowing conversions (11.6.4)
积分转换(与将类型更改为等效或更大类型的提升相对)定义如下(7.8 /3):
A prvalue of an integer type can be converted to a prvalue of another integer type.
7.8/5 然后从积分 conversions 中排除积分 promotions。 这意味着 conversions 通常是 缩小范围 类型更改。
缩小转换范围(您会记得,它被排除在converted constant expressions数组大小)在列表初始化的上下文中定义,11.6.4,par。 7
A narrowing conversion is an implicit conversion
[...]
7.31 — from an integer type [...] to an integer type that cannot represent all the values of the original type, except where the source is a constant expression whose value after integral promotions will fit into the target type.
这实际上是说有效数组大小必须是显示时的常量值,这是避免意外的完全合理要求。
现在让我们把它拼凑起来。工作假设是 std::size_t
是一个 16 位无符号整数类型,取值范围为 0..65535。整数文字 65537
在系统的 16 位 unsigned int
中不可表示,因此具有类型 long
。因此它将进行 整数转换。 这将是一个 缩小转换 因为该值无法用 16 位表示 size_t
2,因此11.6.4/7.3中的异常条件"value fits anyway"不适用。
那么这是什么意思?
11.6.4/3.11 是无法从初始化器列表中的项目生成初始化器值的包罗万象的规则。因为初始化列表规则用于数组大小,所以我们可以假设转换失败的包罗万象适用于数组大小常量:
(3.11) — Otherwise, the program is ill-formed.
需要符合标准的编译器才能生成诊断信息,它确实做到了。结案。
1 对,他们细分段落
2 将 65537 的整数值(在任何类型中都可以保存数字——这里可能是一个长整数)转换为 16 位无符号整数是一个定义的操作。 7.8/2 详情:
If the destination type is unsigned, the resulting value is the least unsigned integer congruent to the source
integer (modulo 2n where n is the number of bits used to represent the unsigned type). [ Note: In a two’s
complement representation, this conversion is conceptual and there is no change in the bit pattern (if there is
no truncation). —end note ]
65537的二进制表示为1_0000_0000_0000_0001
,即只设置低16位的最低位。转换为 16 位无符号值(间接证据表明 size_t
是)计算 [表达式值] 模 2^16,即只取低 16 位。这导致编译器诊断中提到的值为 1。
[这个答案是在问题被标记为C和C++时写的。我还没有 re-examined 鉴于 OP 透露他们使用的是 C++ 而不是 C。]
size_t
是 C 标准指定用于 object 大小的类型。但是,它不是 cure-all 获得正确尺寸的方法。
size_t
应在 <stddef.h>
header 中定义(以及在其他 header 中)。
C 标准不要求数组大小的表达式在声明中指定时具有 size_t
类型,也不要求它们适合 size_t
。没有指定 C 实现在不能满足数组大小的请求时应该做什么,尤其是对于可变长度数组。
在您的代码中:
const uint32_t N = 65537;
uint8_t values[N];
values
声明为可变长度数组。 (虽然我们可以看到 N
的值在编译时很容易知道,但它不符合 C 对常量表达式的定义,因此 uint8_t values[N];
有资格作为可变长度数组的声明。)您观察到,GCC 警告您 32 位无符号整数 N
缩小为 16 位无符号整数。 C 标准不需要此警告;这是编译器提供的礼貌。更重要的是,根本不需要转换——因为 C 标准没有指定数组维度的类型,编译器可以在这里接受任何整数表达式。因此,它已将隐式转换插入到数组维度所需的类型并警告您这一事实是编译器的一项功能,而不是 C 标准的功能。
想想如果你这样写会发生什么:
size_t N = 65537;
uint8_t values[N];
现在 uint8_t values[N];
中不会有警告,因为在需要 16 位整数的地方使用了 16 位整数(C 实现中 size_t
的宽度)。但是,在这种情况下,您的编译器可能会在 size_t N = 65537;
中发出警告,因为 65537
将具有 32 位整数类型,并且在 N
的初始化期间执行缩小转换。
但是,您使用的是可变长度数组这一事实表明您可能正在计算 run-time 处的数组大小,这只是一个简化的示例。可能您的实际代码不使用这样的常量大小;它可能会在执行期间计算大小。例如,您可以使用:
size_t N = NumberOfGroups * ElementsPerGroup + Header;
在这种情况下,有可能会计算出错误的结果。如果所有变量的类型都是 size_t
,结果可能很容易换行(有效地溢出 size_t
类型的限制)。在这种情况下,编译器不会给你任何警告,因为值都是相同的宽度;没有缩小转换,只是溢出。
因此,使用size_t
不足以防止数组维数错误。
另一种方法是使用您希望足够宽的类型来进行计算,也许 uint32_t
。给定 NumberOfGroups
等 uint32_t
类型,则:
const uint32_t N = NumberOfGroups * ElementsPerGroup + Header;
将为 N
生成正确的值。然后可以在run-time测试一下,防止出错:
if ((size_t) N != N)
Report error…
uint8_t values[(size_t) N];
为 8 位微控制器编译 avr-gcc
中的一些测试代码,行
const uint32_t N = 65537;
uint8_t values[N];
我收到以下编译警告(默认情况下应该是一个错误,真的)
warning: conversion from 'long unsigned int' to 'unsigned int' changes value from '65537' to '1' [-Woverflow]
uint8_t values[N];
请注意,为此目标编译时,sizeof(int)
为 2。
看来,数组的大小不能超过unsigned int
的大小。
我说的对吗?这是特定于 GCC 的还是某些 C 或 C++ 标准的一部分?
在有人评论说 8 位微控制器通常没有足够的内存来容纳这么大的数组之前,我只想说这不是重点。
size_t
被认为是要使用的类型,尽管没有被 C 或 C++ 标准正式批准。
这样做的理由是 sizeof(values)
将是那种类型( 是 由 C 和 C++ 标准规定),并且元素的数量将是不一定大于此值,因为对象的 sizeof
至少为 1.
sizeof
返回的值将是 size_t
类型。
它通常用作数组中元素的数量,因为它足够大。 size_t
始终是无符号的,但它是实现定义的类型。最后,它是由实现定义的,实现是否可以支持甚至 SIZE_MAX
字节的对象......甚至接近它。
在您的实现中,size_t
定义为 unsigned int
,uint32_t
定义为 long unsigned int
。创建 C 数组时,数组大小的参数会被编译器隐式转换为 size_t
。
这就是您收到警告的原因。您使用 uint32_t
指定数组大小参数,该参数被转换为 size_t
并且这些类型不匹配。
这可能不是您想要的。请改用 size_t
。
So it seems that, at an array size cannot exceed the size of an
unsigned int
.
在您的特定 C[++] 实现中似乎是这种情况。
Am I correct? Is this gcc-specific or is it part of some C or C++ standard?
它通常不是 GCC 的特性,也不是 C 或 C++ 标准指定的。它是您的特定 实现的特征:适用于您的特定计算平台的 GCC 版本。
C 标准要求指定数组元素数的表达式具有整数类型,但并未指定特定类型。我确实认为您的 GCC 似乎声称它为您提供了一个包含与您指定的元素数量不同的数组,这很奇怪。我认为这不符合标准,而且我认为作为扩展没有多大意义。我宁愿看到它拒绝代码。
我将用 "incorrekt and incomplet" ISO CPP 标准草案 n4659 中的规则来剖析这个问题。重点是我加的。
11.3.4 定义数组声明。第一段包含
If the constant-expression [between the square brackets] (8.20) is present, it shall be a converted constant expression of type std::size_t [...].
std::size_t
来自 <cstddef>
并定义为
[...] an implementation-defined unsigned integer type that is large enough to contain the size in bytes of any object.
由于它是通过 C 标准库头文件导入的,因此 C 标准与 size_t
的属性相关。 ISO C 草案 N2176 在 7.20.3 中规定了整数类型的 "minimal maximums",如果需要的话。对于 size_t
,最大值为 65535。换句话说,16 位 size_t
完全符合。
一个"converted constant expression"定义在8.20/4:
A converted constant expression of type T is an expression, implicitly converted to type T, where the converted expression is a constant expression and the implicit conversion sequence contains only [any of 10 distinct conversions, one of which concerns integers (par. 4.7):]
— integral conversions (7.8) other than narrowing conversions (11.6.4)
积分转换(与将类型更改为等效或更大类型的提升相对)定义如下(7.8 /3):
A prvalue of an integer type can be converted to a prvalue of another integer type.
7.8/5 然后从积分 conversions 中排除积分 promotions。 这意味着 conversions 通常是 缩小范围 类型更改。
缩小转换范围(您会记得,它被排除在converted constant expressions数组大小)在列表初始化的上下文中定义,11.6.4,par。 7
A narrowing conversion is an implicit conversion
[...]
7.31 — from an integer type [...] to an integer type that cannot represent all the values of the original type, except where the source is a constant expression whose value after integral promotions will fit into the target type.
这实际上是说有效数组大小必须是显示时的常量值,这是避免意外的完全合理要求。
现在让我们把它拼凑起来。工作假设是
std::size_t
是一个 16 位无符号整数类型,取值范围为 0..65535。整数文字 65537
在系统的 16 位 unsigned int
中不可表示,因此具有类型 long
。因此它将进行 整数转换。 这将是一个 缩小转换 因为该值无法用 16 位表示 size_t
2,因此11.6.4/7.3中的异常条件"value fits anyway"不适用。
那么这是什么意思?
11.6.4/3.11 是无法从初始化器列表中的项目生成初始化器值的包罗万象的规则。因为初始化列表规则用于数组大小,所以我们可以假设转换失败的包罗万象适用于数组大小常量:
(3.11) — Otherwise, the program is ill-formed.
需要符合标准的编译器才能生成诊断信息,它确实做到了。结案。
1 对,他们细分段落
2 将 65537 的整数值(在任何类型中都可以保存数字——这里可能是一个长整数)转换为 16 位无符号整数是一个定义的操作。 7.8/2 详情:
If the destination type is unsigned, the resulting value is the least unsigned integer congruent to the source integer (modulo 2n where n is the number of bits used to represent the unsigned type). [ Note: In a two’s complement representation, this conversion is conceptual and there is no change in the bit pattern (if there is no truncation). —end note ]
65537的二进制表示为1_0000_0000_0000_0001
,即只设置低16位的最低位。转换为 16 位无符号值(间接证据表明 size_t
是)计算 [表达式值] 模 2^16,即只取低 16 位。这导致编译器诊断中提到的值为 1。
[这个答案是在问题被标记为C和C++时写的。我还没有 re-examined 鉴于 OP 透露他们使用的是 C++ 而不是 C。]
size_t
是 C 标准指定用于 object 大小的类型。但是,它不是 cure-all 获得正确尺寸的方法。
size_t
应在 <stddef.h>
header 中定义(以及在其他 header 中)。
C 标准不要求数组大小的表达式在声明中指定时具有 size_t
类型,也不要求它们适合 size_t
。没有指定 C 实现在不能满足数组大小的请求时应该做什么,尤其是对于可变长度数组。
在您的代码中:
const uint32_t N = 65537;
uint8_t values[N];
values
声明为可变长度数组。 (虽然我们可以看到 N
的值在编译时很容易知道,但它不符合 C 对常量表达式的定义,因此 uint8_t values[N];
有资格作为可变长度数组的声明。)您观察到,GCC 警告您 32 位无符号整数 N
缩小为 16 位无符号整数。 C 标准不需要此警告;这是编译器提供的礼貌。更重要的是,根本不需要转换——因为 C 标准没有指定数组维度的类型,编译器可以在这里接受任何整数表达式。因此,它已将隐式转换插入到数组维度所需的类型并警告您这一事实是编译器的一项功能,而不是 C 标准的功能。
想想如果你这样写会发生什么:
size_t N = 65537;
uint8_t values[N];
现在 uint8_t values[N];
中不会有警告,因为在需要 16 位整数的地方使用了 16 位整数(C 实现中 size_t
的宽度)。但是,在这种情况下,您的编译器可能会在 size_t N = 65537;
中发出警告,因为 65537
将具有 32 位整数类型,并且在 N
的初始化期间执行缩小转换。
但是,您使用的是可变长度数组这一事实表明您可能正在计算 run-time 处的数组大小,这只是一个简化的示例。可能您的实际代码不使用这样的常量大小;它可能会在执行期间计算大小。例如,您可以使用:
size_t N = NumberOfGroups * ElementsPerGroup + Header;
在这种情况下,有可能会计算出错误的结果。如果所有变量的类型都是 size_t
,结果可能很容易换行(有效地溢出 size_t
类型的限制)。在这种情况下,编译器不会给你任何警告,因为值都是相同的宽度;没有缩小转换,只是溢出。
因此,使用size_t
不足以防止数组维数错误。
另一种方法是使用您希望足够宽的类型来进行计算,也许 uint32_t
。给定 NumberOfGroups
等 uint32_t
类型,则:
const uint32_t N = NumberOfGroups * ElementsPerGroup + Header;
将为 N
生成正确的值。然后可以在run-time测试一下,防止出错:
if ((size_t) N != N)
Report error…
uint8_t values[(size_t) N];