我有一个丑陋的函数,我觉得整个strncpy
应该只是一个strcpy
:
void PackData(char*& cursor, const std::string& data) {
*(reinterpret_cast<int*>(cursor)) = static_cast<short>(data.length() + 1);
cursor += sizeof(int);
// copy the text to the buffer
::strncpy(cursor, data.c_str(), data.size());
cursor += (data.length() * sizeof(char));
*(reinterpret_cast<char*>(cursor)) = 0;
cursor += sizeof(char);
}
保证cursor
有足够的空间来包含所有复制的数据。而data
在终止时只包含一个'\0'
字符。
我想更新这个函数以使用strcpy
,并删除一些丑陋的内容。我现在拥有的是:
void PackData(char*& cursor, const std::string& data) {
const int size = data.size() + 1;
std::copy_n(cursor, sizeof(int), reinterpret_cast<char*>(&size));
cursor += sizeof(int);
strcpy(cursor, data.c_str());
cursor += size;
}
我的代码运行良好,但我想问是否有人看到了我可能错过的任何不当行为?
发布于 2016-01-21 15:03:00
新版本的您的功能没有任何实际的错误,我可以发现。您已经将对*(reinterpret_cast<int*>(cursor))
的分配替换为对std::copy_n
的调用,这是很好的,因为旧版本(想必)违反了严格的别名规则,因此调用了未定义的行为。(也可以考虑对齐。)在您的旧函数中,将data.length() + 1
传递给short
而不是int
似乎也是错误的。
不过,我还是要说几句“软”的话。
引用一个指针,然后修改,在我看来并不好看。相反,我会按值获取指针,并将其前进的字节数return
。
std::size_t PackData(char * cursor, const std::string& data);
它看起来更好,并允许更灵活的使用。
C标准库中的字符串操作函数都对以NUL结尾的字符串进行操作。也就是说,std::strcpy
必须查看字符串的每个字节,以确定它是否是字符串的末尾。您已经提到,您的字符串今天不包含NUL字节。但如果它将来会这样做呢?你会记得更新函数吗?即使您确信NUL字节永远不会是一个问题,它仍然没有效率比它可能是。因为std::string
已经知道了它自己的长度,所以有几个选项可用。
std::memcpy
--如果你喜欢用C方式做事std::copy
-如果你喜欢用STL的方式做事std::string::copy
-如果你喜欢用OO的方式做事请注意,这些函数都不会终止复制的字符串,因此您必须自己添加NUL字节。做起来不难。
cursor[size] = '\0';
使用std::memcpy
也可以帮助您存储长度。因为它的参数是void
指针,所以不需要转换任何东西。
std::memcpy(cursor, &size, sizeof(size));
在我看来比
std::copy_n(cursor, sizeof(int), reinterpret_cast<char*>(&size));
您的代码三次提到size
的类型。一次在声明中--这很好--然后在sizeof
表达式中进行两次。通过使用sizeof(size)
而不是sizeof(int)
,您可以消除这种冗余。如果您曾经更改过数据类型,您只需要在一个地方这样做,并且不能使其不一致。
演员
const int size = data.size() + 1;
看起来挺安全的。但你确定吗?整数溢出是一个令人讨厌的错误来源和一个潜在的进入门的饼干。我推荐两件事。
首先,通过拼写清楚地说明您正在缩小范围的事实。
const auto size = static_cast<int>(data.size() + 1);
请注意,为了遵循上一节中的建议,我现在可以使用auto
。
其次,添加防御性代码(在转换之前)以验证您的假设。
assert(INT_MAX > data.size());
如果只使用可信输入调用函数,则这是安全的。如果攻击者担心,assert
是错误的工具,因为它可能被编译出来,并且您不希望您的调试构建是安全的,只在您的发布版本中打开安全漏洞。因此,这样的显式检查如下
if (INT_MAX <= data.size())
throw std::invalid_argument {"data too large"};
在这种情况下会更好。
只要是sizeof(int) <= sizeof(std::string::size_type)
,这段代码是安全的,我认为在几乎任何实现中都是正确的。但是,它仍然可能触发编译器警告比较有符号整数和无符号整数。实际上,size
可能是unsigned
。
或者,您可以使用辅助函数checked_cast<int>(data.size() + 1)
。我认为这个论点不可能溢出,因为std::string
总是必须考虑到终止缓冲区的可能性。
我不知道调用函数的环境,但是cursor
所指向的缓冲区是对齐的吗?如果是这样的话,请注意字符串结束后未使用的填充字节以及写入缓冲区的下一件事。他们可能泄露你不愿让别人看到的信息。
https://codereview.stackexchange.com/questions/117494
复制相似问题