C++ 为什么映射的文件::数据返回char*而不是void*

C++ 为什么映射的文件::数据返回char*而不是void*,c++,boost,boost-iostreams,C++,Boost,Boost Iostreams,或者更好的模板 如果内存映射文件包含一个32位整数序列,如果data()返回一个void*,我们可以直接静态转换到std::uint32\t 为什么boost作者选择返回一个char* 编辑:如前所述,如果便携性是一个问题,则需要翻译。但是,如果说一个文件(或者在本例中是一块内存)是一个字节流,而不是一个比特流,或者IEEE754双倍流,或者是复杂的数据结构,在我看来,这似乎是一个非常宽泛的说法,需要更多的解释 即使必须处理endianness,也能够直接映射到建议的(和此处实现的)be_uin

或者更好的模板

如果内存映射文件包含一个32位整数序列,如果
data()
返回一个
void*
,我们可以直接静态转换到
std::uint32\t

为什么boost作者选择返回一个
char*

编辑:如前所述,如果便携性是一个问题,则需要翻译。但是,如果说一个文件(或者在本例中是一块内存)是一个字节流,而不是一个比特流,或者IEEE754双倍流,或者是复杂的数据结构,在我看来,这似乎是一个非常宽泛的说法,需要更多的解释

即使必须处理endianness,也能够直接映射到建议的(和此处实现的)be_uint32_t向量,这将使代码更具可读性:

struct be_uint32_t {
  std::uint32_t raw;
  operator std::uint32_t() { return ntohl(raw); }
};

static_assert(sizeof(be_uint32_t)==4, "POD failed");
是否允许/建议强制转换为
be\u uint32\u t*
?为什么,或者为什么不

应该使用哪种类型的铸件

EDIT2:由于似乎很难直截了当地讨论阐述者的记忆模型是由位、字节还是单词组成的,因此我将重新措辞,给出一个示例:

#include <cstdint>
#include <memory>
#include <vector>
#include <iostream>
#include <boost/iostreams/device/mapped_file.hpp>

struct entry {
  std::uint32_t a;
  std::uint64_t b;
} __attribute__((packed)); /* compiler specific, but supported 
                              in other ways by all major compilers */

static_assert(sizeof(entry) == 12, "entry: Struct size mismatch");
static_assert(offsetof(entry, a) == 0, "entry: Invalid offset for a");
static_assert(offsetof(entry, b) == 4, "entry: Invalid offset for b");

int main(void) {
  boost::iostreams::mapped_file_source mmap("map");
  assert(mmap.is_open());
  const entry* data_begin = reinterpret_cast<const entry*>(mmap.data());
  const entry* data_end = data_begin + mmap.size()/sizeof(entry);
  for(const entry* ii=data_begin; ii!=data_end; ++ii)
    std::cout << std::hex << ii->a << " " << ii->b << std::endl;
  return 0;
}
#包括
#包括
#包括
#包括
#包括
结构条目{
标准:uint32_t a;
标准:uint64_t b;
}_uuu属性uuu((压缩));/*特定于编译器,但受支持
在其他方面,所有主要的编译器*/
静态_断言(sizeof(entry)==12,“条目:结构大小不匹配”);
静态_断言(offsetof(entry,a)==0,“entry:a的无效偏移量”);
静态_断言(offsetof(entry,b)==4,“entry:Invalid offset for b”);
内部主(空){
boost::iostreams::mapped_file_source mmap(“map”);
断言(mmap.is_open());
常量条目*data\u begin=reinterpret\u cast(mmap.data());
常量条目*data\u end=data\u begin+mmap.size()/sizeof(条目);
对于(常数项*ii=数据开始;ii!=数据结束;++ii)

std::cout
char*
表示原始字节的数组,在大多数情况下,映射的文件::数据就是这个数组

void*
可能会产生误导,因为它提供的有关所包含类型的信息较少,并且需要更多的设置才能使用
char*
-我们知道,文件内容是一些字节,而
char*
表示这些字节

模板返回类型需要在库内部执行到该类型的转换,而在调用方执行转换更有意义(因为库只提供原始文件内容的接口,调用方明确知道这些内容是什么)

如果内存映射文件包含一个32位整数序列,如果
data()
返回一个
void*
,我们可以直接静态转换到
std::uint32\t

不,不是真的。你还需要考虑(如果没有别的)迂回。这个“一步转换”。idea会让你产生一种错误的安全感。你忘记了文件中的字节和你想进入程序的32位整数之间的整个转换层。即使这种转换碰巧对你当前的系统没有作用,对于给定的文件,它仍然是一个转换步骤


最好是得到一个字节数组(确切地说是
char*
所指向的!),然后你知道你必须做一些思考,以确保你的指针转换是有效的,并且你正在执行任何其他需要的工作。

返回
char*
似乎只是一个(特殊的)
boost::iostreams
实现的设计决策

其他API,如返回
void*

正如sehe所观察到的,UNIX规范(和)也使用
void*

这有点像一个复制品

需要注意的是,当从一个体系结构写入内存并在另一个体系结构上读取时,可能需要Lightness在另一个答案中提到的翻译层。使用转换类型很容易解决Endianness问题,但也需要考虑对齐

关于静态演员阵容:提到:

可以创建指向void(可能是cv限定)的指针类型的prvalue 转换为指向任何类型的指针。如果原始指针的值 满足目标类型的对齐要求,然后 结果指针值不变,否则未指定。 将任意指针转换为指向void的指针,再转换回指向void的指针 原始(或更多cv限定)类型保留其原始值


因此,如果要进行内存映射的文件是在具有不同对齐方式的不同体系结构上创建的,则加载可能会失败(例如,使用SIGBUS)取决于体系结构和操作系统。

您可以使用
重新解释\u cast
void
什么都不是。取消对它的引用是无用的。MMAP不是设计为useless@sehe:void的意思是:“我不知道我指的是什么,请确保在访问数据之前执行此操作!”。对我来说,这比确定它指向字节更有意义,即使它确实指向小尾端32位整数!关键是映射文件确实知道。它指向bytes@sehe:不,它指向我在文件中写入的内容。对于endianness,我建议创建一组类,如
le\u uint32\t
,它们具有
uint32\t
转换和
运算符=
,但现在要避免使用构造函数,或是任何在C++11中保持PODness的方法。也不确定字符是否保证是字节。从翻译的角度来看,char*可能比void*更容易误导人。(顺便说一句:很好,你指出了错误的安全感,请注意这对于所有未经测试的软件来说都是常见的:)在一般情况下,你是否建议将字节复制到目标数据结构,以便将它们逐个保存在正确的位置,而不是直接映射到打包结构