C语言 整数提升是否占用更多内存?

e0bqpujr  于 2023-01-12  发布在  其他
关注(0)|答案(5)|浏览(113)

我听说C可以将任何少于integer的数据类型转换为integer类型。这也会影响内存吗?例如,如果我有一个char c,它应该占用内存中的1个字节。在integer提升后,char c会占用内存中的sizeof(int)字节吗?还是integer提升只在操作中进行,因此不会影响内存?

umuewwlo

umuewwlo1#

整数提升仅用于表达式的计算,而不用于对象的存储。
与C标准中的大多数内容一样,标准至少有两个级别来指定行为:计算是用一个抽象计算机模型来描述的,这个抽象计算机按照标准描述的字面意思来执行计算,但是实际的C实现可以用任何方式来执行计算,只要得到相同的可观察行为(主要是输入/输出交互和volatile对象的使用)。所以表达式求值将得到与使用整数提升相同的结果,但是,当涉及到存储对象时,编译器具有很大的自由度。
对于局部变量(在函数内部声明的自动对象),编译器可以将它们保存在寄存器和/或堆栈上。当对象的值在寄存器中时,它通常会占用整个寄存器,因此整个32位寄存器可以用于8位char对象。在堆栈上,编译器可以“打包”对象,也可以不“打包”对象。这取决于目标处理器的特性和优化设置。因此,我们可能会发现char对象在堆栈上只占用一个字节,或者我们可能会发现它们占用32位,或者其他可能性。
然而,对于对象数组,编译器通常使用对象的名义大小。nchar数组将使用 n 字节,n 16位short数组将使用2n 8位字节,以此类推。这可能有例外。例如,如果编译器完全优化了只有很少元素的数组的使用,生成的代码可能就像数组元素是单独声明的对象一样。2然而,在很多情况下,编译器没有选择:如果一个数组被传递给另一个翻译单元中的例程,编译器必须在内存中以其正式定义的形式呈现该数组。对于charshort数组,它们将是其标称大小的连续元素,而不会升级。

ibrsph3r

ibrsph3r2#

不。升级是一个概念性的操作,而不是实际更改内存中的原始变量。
如果变量被用作函数的参数或运算符的操作数,那么当变量的值被复制到CPU寄存器或函数参数的内存位置时,升级就会发生。目标已经有了更大的大小。原始值仍然是它的原始大小。

ukqbszuj

ukqbszuj3#

看起来你的“内存”和内存对齐中的c类型大小不匹配。
C类型有固定的大小(但是对于特定于编译器的类型..嗯,这取决于编译器)。让我们坚持C类型。例如uint16_t应该是16位,总是!这对其他类型是一样的。你可以用sizeof()得到一个类型的实际大小。它适用于标准类型,结构等。
内存对齐是关于“数据如何安装到内存中”。例如,如果你想要一个16位的数据在32位的边界上对齐,那么编译器会把数据放在32位内存地址的开头。
根据编译器优化和数据之前的内容,您可能会丢失多达24位的内存。
在GCC中,可以在变量声明之后添加__attribute__((ALIGNED(8)))来更改内存对齐,其中8是要对齐的字节数。在本例中,8表示64位对齐。
注意,除了“用户”的观点之外,只要目标行为是一致的,编译器可以执行任何它想要的优化,我的意思是,你的数据可以占用它所需要的更多空间,或者只是必要的数量!

q35jwt9p

q35jwt9p4#

C语言是为16位机器设计的,它包含了可以加载或存储16位字的一半的指令,但其他的一切都使用16位操作。给定char1 = (char2+char3) >> 1;这样的东西,使代码表现得好像加法和移位使用8位值将需要包括一个额外的步骤来截断16位加法的结果。
虽然C编译器所针对的一些机器能够比16位操作更快地处理8位操作,但是用于这样的机器的编译器通常包括检查计算的上半部分是否将被观察到的逻辑,并且如果没有被观察到则忽略将计算它的操作。

unsigned char a,b,c;
a=b+c;

编译器将注意到,虽然b+c可能产生超过255的值,并且其上半部分因此将是非零的,但是对a的赋值将丢弃正被存储的值的上半部分,并且因此将不需要计算它。

if (a+b > c)

需要编译器计算(a+b)的16位值并将其与c比较,或者生成代码,该代码使用机器的8位add指令将b加到a,检查是否有进位,如果没有,则将结果与c比较;如果原始计算成功,则可以跳过比较,因为已知结果大于可以保持的最大值X1 M9 N1 X。
在大多数情况下,升级简化了编译器,同时使程序员的生活更美好。主要的陷阱出现在编译器在标准发布前的日子里已经明智地处理了哪些情况,以及哪些实现被期望继续明智地处理,但一些编译器却不遵守这样的先例。例如,如果int是32位,而unsigned short是16位,则编译器传统上将处理:

uint1 = ushort1 * ushort2;

以不可知无符号短值是被提升为signed int还是unsigned int的方式;该标准的作者在出版的基本原理中观察到,用于静音绕回二进制补码机的实现将计算一个值,该值在被转换为无符号时将产生正确的结果,即使该数学乘积大于INT_MAX。然而,如果GCC编译器观察到,如果接收到某些输入,则这样的计算将产生大于INT_MAX的值,它可能会想尽办法避免生成将有意义地处理这些输入的代码。

kadbb459

kadbb4595#

整数提升发生在表达式求值时,因此它不会影响变量的存储数据大小。然而,它可能会也可能不会影响所使用的临时堆栈/寄存器空间。例如,它可能会导致8位CPU上不必要的缓慢计算。编译器可能会优化表达式,以便在比int更小的类型上进行计算。但仅考虑由隐式提升引起的所有可能的副作用。
示例:

uint8_t a = 255;
uint8_t b = 1;
uint8_t c = a+b

这等价于(uint8_t)( (int)a + (int)b )。但是(int)255 + (int)1给出256,当赋值给较小的无符号类型时,只保留最低有效字节,结果为1。因此,它等价于使用带无符号回绕的8位寄存器进行加法。
因此,编译器***可能***会优化整数提升,并使用8位类型计算所有内容,因为它不会改变结果。
示例:

uint8_t a = 0;
uint8_t b = ~a >> 1;

这等效于(uint8_t)( ~(int)a >> 1)。由于aint,因此~将给出值0xFFFF ......,并且由于int也是带符号的,因此符号位将翻转。负结果的右移是实现定义的,C允许它是算术移位或逻辑移位。在算术移位的情况下,符号被保留,并且数据将仍然具有值0xFFFF ...,当其被转换为uint8_t时,意味着我们最终得到0xFF作为结果。
编译器***可能不会***优化掉整数提升,因为本例中的整数提升会影响结果,因为(可能是意外的)改变操作数类型的符号的副作用。如果这是作为(uint8_t)~a >> 1执行的,那么结果将是0x7F。使用这种类型转换使代码具有确定性和可移植性,因此它应该在第一位置显式地完成,而且它还允许编译器在8位寄存器中执行所有操作,这是以前无法做到的。
最佳做法:

  • 不要编写包含或依赖隐式升级的代码。
  • 不要对有符号类型执行按位运算。

理解这种微妙的废话,这是怎么回事字里行间是至关重要的编写无错误的程序为8位或16位CPU。为什么我坚持说,C编程8位,如AVR或PIC是 * 不 * 初学者友好的所有。它更容易编程的CPU,一切归结为32位数字,这反过来意味着少得多的隐式促销。

相关问题