在avr-gcc
中为8位微控制器(该行)编译一些测试代码
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位微控制器通常没有足够大的内存来容纳如此大的阵列之前,让我先预言一下这是不重要的。
答案 0 :(得分:8)
size_t
未被C或C ++标准正式批准,但仍被视为要使用的类型。
这样做的理由是sizeof(values)
将是该类型( 由C和C ++标准管理),并且元素的数量必须不大于此数量。因为对象的sizeof
至少为1。
答案 1 :(得分:7)
因此,似乎数组的大小不能超过
unsigned int
。
在您的特定C [++]实现中,情况似乎是 。
我正确吗?这是gcc特定的,还是某些C或C ++的一部分 标准吗?
通常,它不是GCC的特征,也不由C或C ++标准指定。它是您特定的实现的一个特征:适用于您的特定计算平台的GCC版本。
C标准要求使用表达式来指定数组的元素数具有整数类型,但是它没有指定特定的整数。我确实觉得很奇怪,您的GCC似乎声称给您的数组包含的元素数量与您指定的数量不同。我认为这不符合标准,并且我认为将其作为扩展没有太大意义。我希望看到它拒绝代码。
答案 2 :(得分:4)
在您的实现中,size_t
被定义为unsigned int
,而uint32_t
被定义为long unsigned int
。创建C数组时,编译器会将数组大小的参数隐式转换为size_t
。
这就是为什么您收到警告的原因。您正在使用uint32_t
指定数组大小参数,该参数将转换为size_t
,并且这些类型不匹配。
这可能不是您想要的。请改用size_t
。
答案 3 :(得分:4)
我将使用“ incorrekt and incomplet” ISO CPP标准草案n4659中的规则来剖析问题。重点是我加了。
11.3.4定义数组声明。第一段包含
如果存在[在方括号之间](8.20)的常数表达式,则它应该是类型为std :: size_t [...]的转换常数表达式。
std::size_t
来自<cstddef>
,定义为
[...]一个实现定义的无符号整数类型,该类型足够大以包含任何对象的字节大小。
由于它是通过C标准库头导入的,因此C标准与size_t
的属性有关。 ISO C草案N2176在7.20.3中规定了整数类型的“最小最大值”。对于size_t
,最大值为65535。换句话说,一个16位size_t
完全符合要求。
在8.20 / 4中定义了一个“转换的常量表达式”:
类型T的转换后的常量表达式是隐式转换为类型T的表达式,其中转换后的表达式是常量表达式,并且隐式转换序列仅包含[10个不同转换中的任何一个,一个其中涉及整数(参数4.7):]
-整体转化(7.8)除了缩小转化范围(11.6.4)
完整的转换(与将类型更改为等效或更大类型的 promotion 相反)的定义如下(7.8 / 3):
整数类型的prvalue可以转换为另一种整数类型的prvalue。
7.8 / 5然后将积分促销从积分转化中排除。。这意味着转化通常是缩小类型更改。
在上下文中定义了缩小的转换(您会记得,将其从用于数组大小的转换常量表达式中排除在允许的转换列表之外)清单初始化的步骤11.6.4,参数。 7
缩小转换是隐式转换
[...]
7.3 1 —从整数类型[...]到不能表示原始类型所有值的整数类型,,除非源是一个常量表达式,其值在整数提升后将适合目标类型。
这实际上是说有效数组的大小必须为显示时的常数,这是避免意外的完全合理的要求。
std::size_t
是16位无符号整数类型,其值范围为0..65535。整数文字65537
在系统的16位unsigned int
中无法表示,因此类型为long
。因此,它将进行整数转换。这将是缩小转换,因为该值在16位size_t
2 ,因此11.6.4 / 7.3中的异常条件“无论如何都适合值”不适用。
那是什么意思?
11.6.4 / 3.11是无法从初始化器列表中的项目生成初始化值的全部规则。因为initializer-list规则用于数组大小,所以我们可以假定转换失败的包罗万象适用于数组大小常量:
(3.11)-否则,程序格式不正确。
需要一个合格的编译器才能生成诊断。案件结案。
2 将65537(任何可以容纳该数字的类型,这里可能是“ long”)的整数值转换为16位无符号整数是已定义的操作。 7.8 / 2详细信息:
如果目标类型是无符号的,则结果值是与源一致的最小无符号整数 整数(模2 n ,其中n是用于表示无符号类型的位数)。 [注意:在两人之间 补码表示,这种转换是概念性的,并且位模式没有变化(如果存在 没有截断)。 —尾注]
65537的二进制表示形式为1_0000_0000_0000_0001
,即仅设置了低16位中的最低有效位。转换为16位无符号值(间接证据表明size_t
是),以模2 ^ 16为模计算[表达式值],即简单地取低16位。这将导致在编译器诊断程序中提到的值为1。
答案 4 :(得分:1)
sizeof
返回的值将为size_t
类型。
它通常用作数组中元素的数量,因为它将具有足够的大小。 size_t
始终是无符号的,但它是实现定义的类型。最后,由实现定义,实现是否可以支持甚至SIZE_MAX
个字节的对象……甚至接近它。
答案 5 :(得分:1)
[此答案是在用C和C ++标记问题时写的。鉴于OP的启示,即他们使用的是C ++而不是C,我尚未对其进行重新检查。]
size_t
是C标准指定用于处理对象大小的类型。但是,这并不是解决尺寸问题的全部方法。
size_t
应该在<stddef.h>
标头(以及其他标头)中定义。
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
的初始化期间会执行变窄转换。>
但是,使用可变长度数组的事实表明您可能正在运行时计算数组大小,这只是一个简化的示例。您的实际代码可能不会使用这样的常量大小;它可以在执行期间计算大小。例如,您可以使用:
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
产生正确的值。然后,您可以在运行时对其进行测试以防止错误:
if ((size_t) N != N)
Report error…
uint8_t values[(size_t) N];