我有一个源文件,其中包含表示图像的大字节数组。
下面是一个示例(实际上它可以是任何随机数据):
const uint8_t image_test_image[] ={
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23,
0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B,
0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
...
);
我有几个这样的源文件,它们占用了我的磁盘上的大量空间。 我可以删除空白区域,但这只能节省一点空间。 我想尝试两种不同的方法在编译时初始化该数组以减少源文件大小。
一种方法是使用 base64 字符串:
const uint8_t image_test_image[] =
base64decode(
"AECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMz"
"Q1Njc4OTo7PD0+P0BBQkNERUZH"
...
);
我认为应该可以制作一个仅包含 C++ 标头的 constexpr base64 解码器,该解码器采用字符串文字。示例如下:https://stackoverflow.com/a/59604914/15307950。但这需要您显式添加大小作为参数。我不想要这样。是否可以从参数值推导出模板参数值?
另一种方法是使用 uint64_t 数组或 uint64_t 初始化列表作为输入。选择某种字节顺序并考虑到字节数组可能不是 8 字节的倍数。
我现在使用C++14,但C++17也是可以的。在这个项目的未来,甚至 C++20 也可能成为可能。使用 std::array 就可以了。
编辑2:
uint64 数组的工作原理:
#include <stdio.h>
#include <stdint.h>
#include <array>
//#define LITTLE_ENDIAN
template <typename... T>
constexpr std::array<uint8_t, sizeof...(T)*8> u64_array_to_u8_array(T&&... t)
{
std::array<uint8_t, sizeof...(T)*8> out{};
std::array<int64_t, sizeof...(T)> in = {t...};
for (size_t i = 0; i < sizeof...(T)*8; ++ i) {
#ifdef LITTLE_ENDIAN
out[i] = uint64_t(in[i/8]) >> ((i%8)*8);//little endian
#else
out[i] = uint64_t(in[i/8]) >> ((7-i%8)*8);//big endian
#endif
}
return out;
}
#ifdef LITTLE_ENDIAN
//little little
const auto image_test_image = u64_array_to_u8_array(
0x0706050403020100,0x0F0E0D0C0B0A0908,
0x1716151413121110,0x1F1E1D1C1B1A1918);
#else
//big endian
const auto image_test_image = u64_array_to_u8_array(
0x0001020304050607,0x08090A0B0C0D0E0F,
0x1011121314151617,0x18191A1B1C1D1E1F
);
#endif
int main() {
for (size_t i = 0; i < sizeof(image_test_image); ++i)
{
printf("0x%02X, ", image_test_image[i]);
if ((i%16==15) && image_test_image[i] != sizeof(image_test_image))
{
puts("");
}
}
return 0;
}
现在我只需要知道如何使用字符串文字实现 constexpr base64 解码。
#include <array>
#include <string> // for std::char_traits
//based on https://stackoverflow.com/a/59604914/15307950
constexpr size_t decodeBase64Length(const char *s)
{
size_t len = std::char_traits<char>::length(s);
if (s[len - 2] == '=')
return (len / 4) * 3 - 2;
else if(s[len -1] == '=')
return (len / 4) * 3 - 1;
else
return (len / 4) * 3 ;
}
constexpr std::array<int, 256> prepareBase64DecodeTable() {
std::array<int, 256> T{ 0 }; // breaks constexpr: T.fill(-1) or missing initialization
for (int i = 0; i < 256; i++)
T[i] = -1;
for (int i = 0; i < 64; i++)
T["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i;
return T;
}
// based on https://stackoverflow.com/a/34571089/3158571
template<int N>
constexpr std::array<std::byte, N> decodeBase64(const char *b64Str)
{
constexpr auto T = prepareBase64DecodeTable();
std::array<std::byte, N> out = { std::byte(0) };
int valb = -8;
for (size_t i = 0, val = 0, posOut = 0; i < std::char_traits<char>::length(b64Str) && T[b64Str[i]] != -1; i++) {
val = (val << 6) + T[b64Str[i]];
valb += 6;
if (valb >= 0) {
out[posOut++] = std::byte((val >> valb) & 0xFF);
valb -= 8;
}
}
return out;
}
//added macro to allow string literal in to be used inline without a define
#define DECODE_B64(b64) decodeBase64<decodeBase64Length(b64)>(b64)
用途:
constexpr auto b64a = DECODE_B64("SGVsbG8=");
在这里测试:https://godbolt.org/z/Esx77W1cG
#include <array>
#include <string> // for std::char_traits
#include <algorithm> // for std::copy_n
//from https://yongweiwu.wordpress.com/2022/06/19/compile-time-strings/
template <size_t N>
struct compile_time_string {
constexpr compile_time_string(const char (&str)[N])
{
std::copy_n(str, N, value);
}
char value[N]{};
};
template <compile_time_string cts>
constexpr auto operator""_cts()
{
return cts;
}
//based on https://stackoverflow.com/a/59604914/15307950
constexpr size_t decodeBase64Length(const char *s)
{
size_t len = std::char_traits<char>::length(s);
if (s[len - 2] == '=')
return (len / 4) * 3 - 2;
else if(s[len -1] == '=')
return (len / 4) * 3 - 1;
else
return (len / 4) * 3 ;
}
constexpr size_t decodeBase64Length(std::string_view s)
{
size_t len = s.length();
if (s[len - 2] == '=')
return (len / 4) * 3 - 2;
else if(s[len -1] == '=')
return (len / 4) * 3 - 1;
else
return (len / 4) * 3 ;
}
constexpr std::array<int, 256> prepareBase64DecodeTable() {
std::array<int, 256> T{ 0 }; // breaks constexpr: T.fill(-1) or missing initialization
for (int i = 0; i < 256; i++)
T[i] = -1;
for (int i = 0; i < 64; i++)
T["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i;
return T;
}
// based on https://stackoverflow.com/a/34571089/3158571
//template <size_t N>
template<auto s>
constexpr std::array<std::byte, decodeBase64Length(s.value)> decodeBase64()
{
constexpr auto T = prepareBase64DecodeTable();
std::array<std::byte, decodeBase64Length(s.value)> out = { std::byte(0) };
int valb = -8;
for (size_t i = 0, val = 0, posOut = 0; i < std::char_traits<char>::length(s.value) && T[s.value[i]] != -1; i++) {
val = (val << 6) + T[s.value[i]];
valb += 6;
if (valb >= 0) {
out[posOut++] = std::byte((val >> valb) & 0xFF);
valb -= 8;
}
}
return out;
}
用途:
constexpr auto b64a = decodeBase64<"SGVsbG8="_cts>();