提问人:Fred Helmers 提问时间:11/14/2023 最后编辑:Fred Helmers 更新时间:11/15/2023 访问量:163
使用 C++17 及更早版本进行编译时字符串压缩
Compile-time string compression with C++17 and earlier
问:
我有一个应用程序,它使用带有长链重复字符的字符串。我想将它们以压缩/混淆的形式添加到二进制文件中。为了简单起见,我目前正在使用修改后的 RLE 算法。
我正在使用以下适用于 C++20 的算法。不幸的是,出于商业原因,现在我也必须支持 C++17。我目前对 C++17 的解决方案是将字符串放在 YAML 文件上,并在构建时生成相应的 .cpp“压缩”文件,然后将其链接到进程中。
做一些研究,我发现这个解决方案适用于霍夫曼,但仅支持 C++20(及更高版本)。
我也见过这个解决方案,但“压缩”数据的大小与原始数据相同。
那么问题来了,我怎样才能用C++17重写以下算法?
#include <cstdint>
#include <algorithm>
#include <iostream>
#include <array>
#include <span>
#include <sstream>
struct Array {
const char* data;
std::size_t size;
};
constexpr std::size_t compress( const char* data, std::size_t size, char* buf ) {
if ( size==0 ) return 0;
std::size_t offset = 0;
char lastch = *data;
std::size_t counter = 0;
auto push = [&]() {
if ( counter <= 3 ) {
for ( int j=0; j<counter; ++j ) buf[offset++] = lastch;
}
else {
buf[offset++] = 0;
buf[offset++] = lastch;
buf[offset++] = counter;
}
counter = 0;
};
lastch = data[0];
counter = 1;
for ( std::size_t j=1; j<size; ++j ) {
if ( (data[j]!=lastch) || (counter==255) ) {
push();
lastch = data[j];
}
counter++;
}
push();
return offset;
}
template< std::size_t N >
struct RawContainer {
char raw_data[N];
constexpr RawContainer( const char (&s)[N] ) {
std::copy(s,s+N,raw_data);
}
constexpr operator const char* () const noexcept {
return data;
}
constexpr auto data() const noexcept {
return raw_data;
}
constexpr auto size() const noexcept {
return N;
}
};
template< auto Container >
struct StringCompressor {
StringCompressor() noexcept {
compress(Container.data(),Container.size(),compressed_data.data());
}
constexpr static auto build_size() noexcept {
char out[Container.size()*3];
return compress(Container.data(),Container.size(),out);
}
std::string str() noexcept {
std::ostringstream out;
out << compressed_data.size() << ": ";
for ( std::size_t j=0; j<compressed_data.size(); ++j ) {
out << (int)compressed_data[j] << " ";
}
return out.str();
}
std::array<char,build_size()> compressed_data;
};
template<RawContainer str>
constexpr StringCompressor<str> operator ""_x() noexcept
{
return StringCompressor<str>();
}
auto value = "aaaabbbbbbbbbbbbbbbbbbbc"_x;
int main() {
std::cout << value.str() << std::endl;
}
Godbolt:https://godbolt.org/z/Eh9fMxW75
注意:为简单起见,未包含解压缩算法。
答:
我可能在这里有一个 C++17 示例,它的额外好处是它不将字符串文字存储在二进制文件中。我检查了相当长的参数(汇编程序中第 35 行 pp. 中的字符串是压缩结果)。
与代码中不同,字符串文字用于初始化 constexpr 数组或指针变量,然后将其提供给简化的压缩器类。大小必须单独计算。每个字符串的代码更多,但文字仍然只出现一次。从外观上看,它可以很容易地生成。
调用端如下所示:
constexpr const char p[] = "aaaabbbbbbbbbbbbbbbbbbbc";
constexpr std::size_t comprSz = compress(p, sizeof(p), nullptr);
constexpr Compressor<comprSz> cpr{p, sizeof(p)};
print(cpr.comprData, comprSz);
可以将大小计算移动到另一个中间类中,但无论出于何种原因,似乎都不可能使用普通字符串文字作为模板参数,以便调用可以是单个语句,就像使用用户文字一样。
这是我的版本。该函数是从您的代码中复制的,没有更改,其余部分从我的评论中移除,并进行了一些琐碎的修改。用法很简单:compress
COMPRESSED_LITERAL("aaaabbbbbbbbbbbbbc")
如您所见,原始字符串和压缩代码都不会在生成的对象中留下任何痕迹。
我没有实现,因为压缩的字符串在生成的程序集中是可见的。我让压缩版本和未压缩版本都输出到只是为了并排比较生成的程序集,尽管压缩版本当然不会输出任何内容,因为开始时嵌入了零。str()
std::cout
我能想到的唯一缺点是您需要使用宏。
评论