在C++中,关于位移和强制转换数据类型
In C++, regarding bit-shifting and casting data types
我最近在 Stack Overflow 上问了一个问题,关于如何将我的数据从一个 16 位整数后跟不确定数量的 void* -cast 内存转换为无符号字符的 std::vector 中,以便使用称为 NetLink 的套接字库,该库使用签名如下所示的函数来发送原始数据:
void rawSend(const vector<unsigned char>* data);
(作为参考,这里是这个问题:将无符号 int + 字符串转换为无符号字符向量(
这个问题得到了成功回答,我感谢那些回答的人。 Mike DeSimone以一个send_message((函数的示例作为回应,该函数将数据转换为NetLink接受的格式(std::vector(,如下所示:
void send_message(NLSocket* socket, uint16_t opcode, const void* rawData, size_t rawDataSize)
{
vector<unsigned char> buffer;
buffer.reserve(sizeof(uint16_t) + rawDataSize);
buffer.push_back(opcode >> 8);
buffer.push_back(opcode & 0xFF);
const unsigned char* base(reinterpret_cast<const unsigned char*>(rawData));
buffer.insert(buffer.end(), base, base + rawDataSize);
socket->rawSend(&buffer);
}
这看起来正是我所需要的,所以我开始编写一个随附的 receive_message(( 函数......
。但我很尴尬地说,我并不完全理解所有的位移等等,所以我在这里遇到了一堵墙。 在我近十年来编写的所有代码中,我的大部分代码都是使用高级语言编写的,而我的其余代码从未真正要求使用较低级别的内存操作。
回到编写 receive_message(( 函数的主题,正如您可能想象的那样,我的起点是 NetLink 的 rawRead(( 函数,其签名如下所示:
vector<unsigned char>* rawRead(unsigned bufferSize = DEFAULT_BUFFER_SIZE, string* hostFrom = NULL);
看起来我的代码将像这样开始:
void receive_message(NLSocket* socket, uint16_t* opcode, const void** rawData)
{
std::vector<unsigned char, std::allocator<unsigned char>>* buffer = socket->rawRead();
std::allocator<unsigned char> allocator = buffer->get_allocator(); // do I even need this allocator? I saw that one is returned as part of the above object, but...
// ...
}
在第一次调用 rawRead(( 之后,我似乎需要遍历向量,从中检索数据并反转位移操作,然后将数据返回到 *rawData 和 *opcode。 同样,我对位移不是很熟悉(我做了一些谷歌搜索来理解语法,但我不明白为什么上面的 send_message(( 代码需要移位(,所以我对下一步不知所措。
有人可以帮助我了解如何编写这个随附的 receive_message(( 函数吗? 作为奖励,如果有人可以帮助解释原始代码,以便我知道它是如何工作的(特别是在这种情况下转移是如何工作的以及为什么它是必要的(,这将有助于加深我对未来的理解。
提前感谢!
库的功能签名...
void rawSend( const vector<unsigned char>* data );
迫使您构建数据std::vector
,这实质上意味着它带来了不必要的低效率。要求客户端代码构建std::vector
没有任何优势。设计它的人不知道他们在做什么,明智的做法是不使用他们的软件。
库函数签名...
vector<unsigned char>* rawRead(unsigned bufferSize = DEFAULT_BUFFER_SIZE, string* hostFrom = NULL);
更糟糕的是:如果你想指定一个"hostFrom"(不管这到底意味着什么(,它不仅不必要地要求你构建一个std::string
,而且它不必要地要求你vector
释放结果。至少如果函数结果类型有任何意义的话。当然,这可能没有。
您不应该使用具有如此令人作呕的函数签名的库。可能任何随机选择的库都会好得多。即,更容易使用。
如何现有的使用代码...
void send_message(NLSocket* socket, uint16_t opcode, const void* rawData, size_t rawDataSize)
{
vector<unsigned char> buffer;
buffer.reserve(sizeof(uint16_t) + rawDataSize);
buffer.push_back(opcode >> 8);
buffer.push_back(opcode & 0xFF);
const unsigned char* base(reinterpret_cast<const unsigned char*>(rawData));
buffer.insert(buffer.end(), base, base + rawDataSize);
socket->rawSend(&buffer);
}
工程:
reserve
调用是过早优化的情况。它尝试使vector
只执行一个缓冲区分配(此时执行(,而不是两个或更多。对于构建vector
的明显低效率,一个更好的治疗方法是使用一个更理智的库。buffer.push_back(opcode >> 8)
将(假定的(16位数量的高8位放在向量的开头,opcode
。首先放置高部分,最重要的部分称为大端格式。另一端的读取代码必须采用大端格式。同样,如果此发送代码使用小端格式,则读取代码必须采用小端格式。因此,这只是一个数据格式决策,但给定该决定,两端的代码都必须遵守它。buffer.push_back(opcode & 0xFF)
调用将低 8 位opcode
放在高位之后,这对于大端是正确的。const unsigned char* base(reinterpret_cast<const unsigned char*>(rawData))
声明只是命名一个指向数据的适当类型的指针,将其称为base
。const unsigned char*
类型是合适的,因为它允许字节级地址算法。原始的形式参数类型const void*
不允许地址算术。buffer.insert(buffer.end(), base, base + rawDataSize)
将数据添加到矢量。表达式base + rawDataSize
是上一个声明启用的地址算术。socket->rawSend(&buffer)
是对 SillyLibraryrawSend
方法的最终调用。
如何包装对 SillyLibrary rawRead
函数的调用。
首先,为字节数据类型定义一个名称(命名事物总是一个好主意(:
typedef unsigned char Byte;
typedef ptrdiff_t Size;
有关如何解除分配/销毁/删除(如有必要(SillyLibrary 函数结果的文档:
void deleteSillyLibVector( vector<Byte> const* p )
{
// perhaps just "delete p", but it depends on the SillyLibrary
}
现在,对于发送操作来说,涉及std::vector
只是一种痛苦。对于接收操作,情况正好相反。创建一个动态数组并将其作为函数结果安全高效地传递,正是std::vector
设计的目的。
但是,发送操作只是一个调用。
对于接收操作,根据 SillyLibrary 的设计,您可以循环执行接收调用次数,直到您收到所有数据。您没有提供足够的信息来执行此操作。但是下面的代码显示了您的循环代码可以调用的底层读取,在vector
中累积数据:
Size receive_append( NLSocket& socket, vector<Byte>& data )
{
vector<Byte> const* const result = socket.raw_read();
if( result == 0 )
{
return 0;
}
struct ScopeGuard
{
vector<Byte>* pDoomed;
explicit ScopeGuard( vector<Byte>* p ): pDoomed( p ) {}
~ScopeGuard() { deleteSillyLibVector( pDoomed ); }
};
Size const nBytesRead = result->size();
ScopeGuard cleanup( result );
data.insert( data.end(), result->begin(), result->end() );
return nBytesRead;
}
请注意使用析构函数进行清理,这使得此异常更加安全。在这种特殊情况下,唯一可能的例外是std::bad_alloc
,无论如何这是非常致命的。但是,为了异常安全,使用析构函数进行清理的一般技术非常值得了解和使用(通常不必定义任何新类,但是在处理SillyLibrary时可能必须这样做(。
最后,当您的循环代码确定所有数据都在手边时,它可以解释vector
中的数据。我把它留作练习,尽管这主要是你所要求的。那是因为我在这里几乎写了整篇文章。
免责声明:即兴代码。
干杯和hth.,
把位摆弄变成非位摆弄的术语,opcode >> 8
等价于opcode / 256
,opcode & 0xFF
等价于opcode - ((opcode / 256) * 256)
。注意舍入/截断。
opcode
由两个块组成,ophi
和oplo
,每个块的值为0..255。 opcode == (ophi * 256) + oplo
.
一些额外的线索...
0xFF == 255 == binary 11111111 == 2^8 - 1
0x100 == 256 == binary 100000000 == 2^8
opcode
/
Binary : 1010101010101010
/ /
ophi oplo
其原因基本上是将 16 位值写入按字节数据流的字节序修复。网络流有自己的规则,在该规则中,必须首先发送值的"大端",而与在任何特定平台上默认如何处理无关。该send_message基本上是解构十六位值以发送它。您需要读取两个块,然后重建十六位值。
无论您将重建编码为opcode = (ophi * 256) + oplo;
还是opcode == (ophi << 8) | oplo;
,主要取决于品味问题 - 优化器将理解等价性并找出最有效的方法。
另外,不,我认为您不需要分配器。我什至不确定使用 vector
是否是一个好主意,因为您使用的是 const void** rawData
参数,但可能是,您应该在阅读之前进行reserve
。然后额外增加相关块(用于重建操作码的两个字节,加上数组内容(。
我看到的一个大问题 - 你怎么知道你将要读取的原始数据的大小?它似乎既不是要receive_message
的参数,也不是数据流本身提供的。
- 防止主数据类型C++的隐式转换
- 处理小于cpu数据总线的数据类型.(c++转换为机器代码)
- 将复杂的非基元C++数据类型转换为 Erlang/Elixir 格式,以使用 NIF 导出方法
- Static_cast转换为错误的数据类型,但结果仍然正确?
- 如何使用 dart:ffi 将 Uint8List 转换为 C 等效数据类型
- 为 Sql 服务器实现 odbc 包装器.将数据库数据读取为字符或要求驱动程序将数据转换为 C 类型
- C++将 wwn 字符串转换为识别为十六进制的数据类型
- 从 cpp lib 调用函数时的数据类型转换
- 如何将PyObjects转换为C数据类型
- 将运算符<<与隐式转换的非基本数据类型一起使用时出错
- 如何将两个 jlong 数据类型转换为 jstring,然后将两个字符串连接在一起以便从 JNI 将字符串返回给 jav
- pybind11:Python 到 C++ 数据类型转换不起作用
- 尝试执行绕道附加,但无法将我的lua函数的数据类型转换为LPVOID
- 使用正确的数据类型转换java中的c++方法逻辑
- 数据类型在 CPP 中将 int 转换为双倍
- 将数据类型从C#封送至C++的类型转换问题
- 数据类型(从 malloc 返回)是如何转换的?
- XLL由XLW制造,BOOST UBLAS MyMatrix数据类型转换为双**失败
- 在C++中为C库强制转换数据类型
- 在C++中,关于位移和强制转换数据类型