给定void *一些存储,如何检查它是否指向正确对齐的存储而没有任何实现定义的行为?
我们当然有std::align,但是有更有效的方法吗?
template <std::size_t alignment>
inline bool is_aligned(void * ptr) noexcept {
std::size_t max = 1u;
return std::align(alignment, 1u, ptr, max);
}
Run Code Online (Sandbox Code Playgroud)
PS:我需要以兼容C++标准的方式执行此操作,而不依赖于任何特定于平台的(实现定义的)黑客攻击.
PPS:我为我(理解)英语而道歉,而不是我的母语.
EDIT(2018.08.24):从标题中删除了"有效",添加了更多的措辞,强调我不希望任何实现定义或特定于平台的行为.
已经建立(见下文)new创建对象需要放置
int* p = (int*)malloc(sizeof(int));
*p = 42; // illegal, there isn't an int
Run Code Online (Sandbox Code Playgroud)
然而,这是在C中创建对象的一种非常标准的方式.
问题是,int如果是在C中创建并返回到C++ ,是否存在?
换句话说,以下是否保证合法?假设intC和C++是相同的.
foo.h中
#ifdef __cplusplus
extern "C" {
#endif
int* foo(void);
#ifdef __cplusplus
}
#endif
Run Code Online (Sandbox Code Playgroud)
foo.c的
#include "foo.h"
#include <stdlib.h>
int* foo(void) {
return malloc(sizeof(int));
}
Run Code Online (Sandbox Code Playgroud)
main.cpp中
#include "foo.h"
#include<cstdlib>
int main() {
int* p = foo();
*p = 42;
std::free(p);
}
Run Code Online (Sandbox Code Playgroud)
有关安置强制性质的讨论的链接new:
似乎有一些协议,char由于C++别名规则,你不能毫无疑问地将一个int(一个int*)指向一个数组.
从另一个问题 - 基于通用char []的存储和避免严格别名相关的UB - 它似乎允许(重新)通过新的放置使用存储.
alignas(int) char buf[sizeof(int)];
void f() {
// turn the memory into an int: (??) from the POV of the abstract machine!
::new (buf) int; // is this strictly required? (aside: it's obviously a no-op)
// access storage:
*((int*)buf) = 42; // for this discussion, just assume the cast itself yields the correct pointer value
}
Run Code Online (Sandbox Code Playgroud)
那么,上面是合法的C++ 并且是实际需要的新版本才能使其合法化吗?
c++ strict-aliasing placement-new primitive-types language-lawyer
我发现C++中的普通类型的复杂性非常重要,需要了解并希望有人可以启发我以下内容.
给定的类型T,存储用于T使用分配::operator new(std::size_t)或::operator new[](std::size_t)或std::aligned_storage,和void * p指向在该存储用于适当对齐的位置T,使得其可以在构造p:
std::is_trivially_default_constructible<T>::value保持,代码是否在代码跳过Tat p(即使用T * tPtr = new (p) T();)的初始化之前调用未定义的行为,否则访问*p为T?T * tPtr = static_cast<T *>(p);在这种情况下,是否可以使用而不用担心未定义的行为?std::is_trivially_destructible<T>::value成立,是否跳过Tat *p(即通过调用tPtr->~T();)的破坏导致未定义的行为?U的std::is_trivially_assignable<T, U>::value保持,std::memcpy(&t, &u, sizeof(U));等同于t = std::forward<U>(u);(对于任何t类型T和u类型U)或它是否会导致未定义的行为?在嵌入式世界中,人们将硬件(-configuration)-register-mappings编写为结构,这是32位硬件的一个非常简单的例子:
#define hw_baseaddr ((uintptr_t) 0x10000000)
struct regs {
uint32_t reg1;
uint32_t reg2;
};
#define hw_reg ((volatile struct regs *) hw_baseaddr)
void f(void)
{
hw_reg->reg1 = 0xdeadcafe;
hw_reg->reg2 = 0xc0fefe;
}
Run Code Online (Sandbox Code Playgroud)
这非常有效,编译器(gcc至少在我们的平台上)认识到它hw_reg引用了相同的地址(在编译时已知并且是常量)并且ld仅仅是一次.第二个st(存储)使用单个指令完成4字节偏移 - 再次在我们的平台上.
如何使用现代C++(后C++ 11)重现这种行为而不使用#defines?
我们尝试了很多东西:static const课堂内外constexpr.他们都不喜欢(隐含)reinterprest_cast<>.
回应关于为什么改变它的评论:我担心它主要是名利双收.但不仅如此.有了这个C代码调试可能很难.想象一下,你想要记录所有的写访问,这种方法需要你重写所有的地方.但是,在这里,我不是在寻找能够简化特定情况的解决方案,我正在寻找灵感.
编辑只是为了澄清一些评论:我要求这个问题不要改变任何有效的代码(并且写于20世纪90年代).我正在寻找未来项目的解决方案,因为我对define-implementation 并不完全满意,并且问自己现代C++是否具有更高的可能性.
请考虑以下示例代码:
class C
{
public:
int* x;
};
void f()
{
C* c = static_cast<C*>(malloc(sizeof(C)));
c->x = nullptr; // <-- here
}
Run Code Online (Sandbox Code Playgroud)
如果由于任何原因我不得不忍受未初始化的内存(当然,如果可能的话,我会打电话new C()),我仍然可以调用放置构造函数.但是,如果我省略这一点,如上所述,并手动初始化每个成员变量,是否会导致未定义的行为?即绕过构造函数本身未定义的行为,或者用类外的一些等效代码替换调用它是否合法?
(通过另一个完全不同的问题遇到这个问题;要求好奇......)
如果所有对象至少有一个构造函数,则由编译器或用户定义的默认c'tor定义,那么对象如何未初始化.
为了克服对齐问题,我需要记忆成一个临时的.临时的那种类型是什么?gcc抱怨以下reinterpret_cast将破坏严格的别名规则:
template <typename T>
T deserialize(char *ptr) {
static_assert(std::is_trivially_copyable<T>::value, "must be trivially copyable");
alignas(T) char raw[sizeof(T)];
memcpy(raw, ptr, sizeof(T));
return *reinterpret_cast<T *>(raw);
}
Run Code Online (Sandbox Code Playgroud)
(例如,当T为"长"时).
我不想定义T,因为我不想在覆盖之前构造T.
在一个联合中,不写一个成员然后读另一个计数作为未定义的行为?
template<typename T>
T deserialize(char *ptr) {
union {
char arr[sizeof(T)];
T obj;
} u;
memcpy(u.arr, ptr, sizeof(T)); // Write to u.arr
return u.obj; // Read from u.obj, even though arr is the active member.
}
Run Code Online (Sandbox Code Playgroud) 在下面的代码中,我在标准的单词(加上P0137的措辞)中对对象生命周期进行了细致的处理.
请注意,根据P0137,所有内存分配都是通过适当对齐的unsigned char类型存储.
还要注意,这Foo是一个POD,带有一个简单的构造函数.
A.如果我误解了标准,并且这里有任何UB,请将其指出(或者确认没有UB)
B. 鉴于构造是微不足道的,并且不执行实际的初始化,A,B,C,D,E,F的初始化是严格必要的.如果是这样,请说明标准的哪一部分在这方面与[object.lifetime]相矛盾或澄清.
#include <memory>
// a POD with trivial constructor
struct Foo
{
int x;
};
struct destroy1
{
void operator()(Foo* p)
{
// RAII to guarantee correct destruction order
auto memory = std::unique_ptr<unsigned char[]>(reinterpret_cast<unsigned char*>(p));
p->~Foo(); // A
}
};
std::unique_ptr<Foo, destroy1> create1()
{
// RAII to guarantee correct exception handling
auto p = std::make_unique<unsigned char[]>(sizeof(Foo));
auto pCandidate = reinterpret_cast<Foo*>(p.get());
new (pCandidate) Foo(); // …Run Code Online (Sandbox Code Playgroud) 我很难理解cppreference引用的有关普通默认构造函数的以下段落。我已经搜索了stackoverflow,但仍然没有一个明确的答案。所以请帮忙。
普通的默认构造函数是不执行任何操作的构造函数。与C语言兼容的所有数据类型(POD类型)都是默认可构造的。但是,与C语言不同,不能通过简单地重新解释适当对齐的存储来创建具有琐碎默认构造函数的对象,例如,使用std :: malloc分配的内存:正式引入新对象并避免潜在的未定义行为时需要placement-new。
具体来说,如果琐碎的默认构造函数什么都不做,为什么我们不能重新解释存储并假装存在具有给定类型的对象?您能否提供一些可能导致未定义行为的示例?