【问题标题】:Is it okay to store different datatypes in same allocated memory in C?可以将不同的数据类型存储在 C 中相同的分配内存中吗?
【发布时间】:2015-12-31 17:03:38
【问题描述】:

我想将不同的数据类型存储在同一个分配的内存中,以通过仅分配一次内存来减少执行时间。我发现实际上可以创建一个 uint8_t 变量数组并创建一个指向同一内存地址的新 uint16_t 指针,然后我可以双向读取值。

这允许我创建一个指针,比如说指向已分配内存的中间,并将后半部分的值存储在不同的数据类型中。

这样做可以吗?我知道我需要注意内存边界,但这是不好的风格吗?

这是我的代码:

#include <stdio.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdlib.h>

int main(void){
    uint8_t *array;
    uint16_t *array2;
    array = calloc(6, 1);
    array[0] = 257;

    printf("array[0]= %" PRIu8 "\n", array[0]);
    printf("array[1]= %" PRIu8 "\n", array[1]);
    printf("Adresse von array[0] = %p\n", &array[0]);
    array2 = &array[0];
    printf("Adresse auf die array2 zeigt = %p\n", array2);

    array2[0] = 257;
    printf("array2[0]= %" PRIu16 "\n", array2[0]);
    printf("array2[1]= %" PRIu16 "\n", array2[1]);
    printf("array[0]= %" PRIu8 "\n", array[0]);
    printf("array[1]= %" PRIu8 "\n", array[1]);

    getchar();
    return 0;
}

【问题讨论】:

  • 了解联合
  • 但实际上你并没有将不同数据类型的数据存储在同一个分配的内存中。
  • 手动执行此操作是一种不好的做法,因为数据类型可能具有不同的内存对齐要求。使用union
  • 你真的不可能加快执行速度。任何体面的标准库都会为您重用内存。这种事情往往只在您分配大块内存时才值得,大到您使用操作系统调用而不是库调用。

标签: c memory memory-management


【解决方案1】:

使用union 创建一个变量,该变量有时存储一种类型,有时存储另一种类型。

union {
  uint8_t u8;
  uint16_t u16;
} *array_u;
size_t nmemb = 6; 

array_u = calloc(nmemb, sizeof *array_u);
assert(array_u);

printf("array_u[0].u8 = %" PRIu8 "\n", array_u[0].u8);

array_u[0].u16 = 1234;
printf("array_u[0].u16 = %" PRIu16 "\n", array_u[0].u16);
...

这不会使用所有空间,每个联合只有一个 uint8_t u8。下面使用 2 个uint8_t

union {
  uint8_t u8[2];
  uint16_t u16;
} *array_u;

printf("array_u[0].u8[0] = %" PRIu8 "\n", array_u[0].u8[0]);

array_u[0].u16 = 1234;
printf("array_u[0].u16 = %" PRIu16 "\n", array_u[0].u16);

OTOH 如果代码需要覆盖整个固定长度的数组

union {
  uint8_t u8[12];
  uint16_t u16[6];
} *array_u;

array_u = calloc(1, sizeof *array_u);
assert(array_u);

printf("array_u->u8[0] = %" PRIu8 "\n", array_u->u8[0]);

array_u->u16[0] = 1234;
printf("array_u->u16[0] = %" PRIu16 "\n", array_u->u16[0]);
...

【讨论】:

  • 很好的答案!非常感谢! array_u-&gt;u16[0]array_u[0].u16[0] 有什么区别吗?
  • @TimFinnegan array_u-&gt;u16[0]array_u[0].u16[0] 之间没有功能差异。使用最能传达代码含义的形式。
【解决方案2】:

这样可以吗?

标准说:

指向对象类型的指针可以转换为指向不同对象类型的指针。如果结果指针未正确对齐引用的类型,则行为未定义。否则,当再次转换回来时,结果将等于原始指针。

(C2011,6.3.2.3/7)

请注意,您甚至不必使用转换后的指针来产生未定义的行为——转换本身的行为是未定义的。

话虽如此,您的示例代码展示了一种特殊情况:成功调用 calloc()(或 malloc()realloc())返回的指针保证可以针对任何类型的对象进行适当对齐,因此您执行的特定指针转换 (array2 = &amp;array[0]) 应该始终没问题。但是,它应该需要演员表。你的编译器应该警告你。

如果您计划将指针转换为分配块内部的任意位置,那么您不能依赖对齐方式是正确的。

另请注意,使用此方案可能会损害性能而不是提高性能。特别是,一些处理器对于适当对齐的数据具有更好的加载和存储性能,即使它们可以处理其他对齐。您为每个块支付一次内存分配成本 - 您为每次访问支付未对齐访问的任何成本。

我知道我需要注意内存边界,但这是不好的风格吗?

是的,非常。您正在牺牲代码清晰度以获得不确定的假设性能增益。增加的开发和维护成本可能会淹没任何性能提升,尤其是因为您也混淆了编译器的意图,从而使生成高效机器代码变得更加困难。

此外,现在开始进行这种微优化可能还为时过早。首先让你的程序正常工作。如果速度不够快,请测试找出瓶颈并专注于改进这些部分。您最有可能通过选择更好的算法来提高性能;你提议的小技巧很少值得。

【讨论】:

  • 对问题的良好分析。
猜你喜欢
  • 2020-03-29
  • 2019-06-24
  • 1970-01-01
  • 1970-01-01
  • 2021-09-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-11
相关资源
最近更新 更多