我正在努力实现共享内存缓冲区而不破坏C99的严格别名规则.
假设我有一些处理一些数据的代码,需要有一些'临时'内存来运行.我可以把它写成:
void foo(... some arguments here ...) {
int* scratchMem = new int[1000]; // Allocate.
// Do stuff...
delete[] scratchMem; // Free.
}
Run Code Online (Sandbox Code Playgroud)
然后我有另一个功能,做一些其他需要一个临时缓冲区的东西:
void bar(...arguments...) {
float* scratchMem = new float[1000]; // Allocate.
// Do other stuff...
delete[] scratchMem; // Free.
}
Run Code Online (Sandbox Code Playgroud)
问题是在操作期间可能会多次调用foo()和bar(),并且在性能和内存碎片方面,整个地方的堆分配可能非常糟糕.一个明显的解决方案是分配一个适当大小的公共共享内存缓冲区,然后将其作为参数传递给foo()和bar(),BYOB样式:
void foo(void* scratchMem);
void bar(void* scratchMem);
int main() {
const int iAmBigEnough = 5000;
int* scratchMem = new int[iAmBigEnough];
foo(scratchMem);
bar(scratchMem);
delete[] scratchMem;
return 0;
}
void foo(void* scratchMem) {
int* smem = (int*)scratchMem;
// …Run Code Online (Sandbox Code Playgroud) 我最近一直试图理解严格别名的一个特定方面,我想我已经制作了尽可能小的有趣代码.(对我来说很有趣,就是!)
更新:根据目前为止的答案,很明显我需要澄清这个问题.从某个角度来看,这里的第一个列表是"明显"定义的行为.真正的问题是遵循这个逻辑到自定义分配器和自定义内存池.如果我malloc在开始时有一大块内存,然后编写我自己的my_malloc并且my_free使用那个单个大块,那么UB是不是因为它不使用官方free?
我会坚持使用C,有点随意.我得到的印象是更容易谈论,C标准更清晰一点.
int main() {
uint32_t *p32 = malloc(4);
*p32 = 0;
free(p32);
uint16_t *p16 = malloc(4);
p16[0] = 7;
p16[1] = 7;
free(p16);
}
Run Code Online (Sandbox Code Playgroud)
第二个可能malloc会返回与第一个相同的地址malloc(因为它free介于两者之间).这意味着它正在访问具有两种不同类型的相同内存,这违反了严格的别名.那么上面肯定是未定义的行为(UB)?
(为简单起见,让我们假设malloc总是成功.我可以添加检查返回值malloc,但这会使问题混乱)
如果不是UB,为什么?标准中是否有明确的例外,它说malloc和free(和calloc/ realloc/ ...)被允许"删除"与特定地址相关的类型,允许进一步访问在地址上"压印"一个新类型?
如果malloc/ free是特殊的,那么这是否意味着我不能合法地编写我自己的克隆行为的分配器malloc?我确信有很多项目都有自定义分配器 - 它们都是UB吗?
因此,如果我们决定必须定义这样的自定义分配器行为,则意味着严格别名规则本质上是"不正确的".我会更新它,说只要你不再使用旧类型的指针,就可以通过不同('new')类型的指针写入(不读取).如果确认所有编译器基本上都遵守了这个新规则,那么这个措辞可能会悄然改变.
我得到的印象是,gcc并且clang基本上尊重我的(积极的)重新解释.如果是这样,也许应该相应地编辑标准?关于 …
考虑一个符合POSIX.1-2008的操作系统,并将fd设为有效的文件描述符(对于打开的文件,读取模式,足够的数据...)。以下代码符合C ++ 11标准*(忽略错误检查):
void* map = mmap(NULL, sizeof(int)*10, PROT_READ, MAP_PRIVATE, fd, 0);
int* foo = static_cast<int*>(map);
Run Code Online (Sandbox Code Playgroud)
现在,以下指令是否违反了严格的别名规则?
int bar = *foo;
Run Code Online (Sandbox Code Playgroud)
根据标准:
如果程序尝试通过以下类型之一以外的glvalue访问对象的存储值,则行为未定义:
- 对象的动态类型,
- 对象动态类型的cv限定版本,
- 与对象的动态类型类似(定义见4.4)的类型,
- 类型是与对象的动态类型相对应的有符号或无符号类型,
- 一种类型,是与对象的动态类型的CV限定版本相对应的有符号或无符号类型,
- 集合或联合类型,在其元素或非静态数据成员(递归包括子集合或包含的联合的元素或非静态数据成员)中包括上述类型之一,
- 该类型是对象动态类型的(可能是cv限定的)基类类型,
- 字符或无符号字符类型。
map / foo指向的对象的动态类型是什么?那甚至是物体吗?该标准说:
类型T对象的生命周期在以下情况下开始:获得具有类型T正确的对齐方式和大小的存储,并且如果对象具有非平凡的初始化,则其初始化完成。
这是否意味着映射的内存包含10个int对象(假设初始地址已对齐)?但是,如果这是真的,那么这是否也不适用于此代码(这显然会破坏严格的别名)?
char baz[sizeof(int)];
int* p=reinterpret_cast<int*>(&baz);
*p=5;
Run Code Online (Sandbox Code Playgroud)
甚至奇怪的是,这是否意味着声明baz开始了大小为4的任何对象(正确对齐)的生存期?
一些情况:我正在映射一个文件,其中包含我希望直接访问的大量数据。由于此块很大,因此我想避免存储到临时对象。
*在这里可以将nullptr代替NULL,是否将其隐式转换为NULL?该标准有什么参考吗?