在C ++中存储非null终止的C字符串常量

时间:2013-05-10 13:33:01

标签: c++ string null-character

在有人说之前,“不要这样做,因为它真的很糟糕。”

  1. 我理解NUL终止字符串的原因。
  2. 我知道可以陈述类似的东西   
    char mystr[] = { 'm', 'y', ' ', 's', 't', 'r', 'i', 'n', 'g'};
    但是,c-string表示的便利性太大了。
  3. 这样做的理由是我正在为微控制器编程,我需要将数据存储到程序的内存中。一些数据是字节,单词,双字和浮点数的形式。我希望数据包含没有NUL连续的字符串。

    我尝试过< size_t N,char * A> < size_t N,char(& A)[N]> 的模板作为参数,以遍历数组并将其内容存储到静态数组,但我似乎无法正确。我认为标准可能实际上不允许这种情况在一般情况下是可以理解的,但在特定情况下是不幸的(特别是,这个。;):()

    如果我可以将字符串重新映射为类似 boost :: mpl :: vector_c< char,...> 的模板,那会更好,因为我有其他代码可以存储它正确,但从模板中取消引用一个数组以用作const模板参数似乎也是不允许的。

    有什么想法吗?

    修改

    Psudocode示例(这有点人为,因为实际代码要大得多,我也不会像这样逐字节读取,也不会使用文字迭代到字符串的末尾。这将是嵌入数据以及某处。):

    // this stores bytes in an array
    template<typename X, typename T, T ...numbers>
    struct x
    {
      static PROGMEM volatile const T data[];
    };
    template<typename X, typename T, T ...numbers>
    PROGMEM volatile const T x<X, T, numbers...>::data[] = { numbers... };
    
    void main()
    {
      // this will not work, but the idea is you have byte 0 as 1, 
      // byte 1 as 2 byte 2 as 3 byte 3 as 's', byte 4 as 'o'...
      // byte 22 as 'g', byte 23 as 4, byte 24 as 5, byte 25 as 6.
      typedef x<int, char, 1,2,3,"some embedded string",4,5,6> xx;
      for(i=0; i<20; ++i)
        Serial.print(pgm_read_byte_near(&xx::data[0] + 3));
    }
    

    另请注意,我没有使用C ++ 11,这是C ++ 0x,可能还有扩展名。

2 个答案:

答案 0 :(得分:3)

第三次尝试

魔术和诡计

如果您使用的是C ++ 11(我知道,但在我缺席的情况下,我认为代码生成是您最好的选择),感觉用户定义的文字应该能够处理这个问题。例如,用:

template <char... RAW>
inline constexpr std::array<char, sizeof...(RAW)> operator "" _fixed() {
    return std::array<char, sizeof...(RAW)>{RAW...};
}

如果这样做会很好:

const std::array<char, 7> goodbye = goodbye_fixed;

...但遗憾的是它没有(文字需要是数字,大概是出于解析的原因)。使用"goodbye"_fixed也不起作用,因为它需要operator "" _fixed(const char *s, int length)重载并且编译时数组已再次衰减为指针。

最后我们来调用这个:

const auto goodbye = operator "" _FS <'g','o','o','d','b','y','e'>();

它并不比丑陋的第一版更好。还有其他想法吗?


第二次尝试

自动生成丑陋

我认为你是对的,你不能轻易拦截字符串文字机制。 老实说,通常的方法是使用构建工具在单独的文件中为您生成丑陋的代码(例如,参见国际化库)。

例如,您输入

fixed_string hello = "hello";

或专用文件中的类似内容,构建系统生成标题

const std::array<char, 5> hello;

和一个cpp,下面是 的丑陋初始化。


首先尝试

错过了“看起来像字符串文字”的要求

  

我试过模板......

像这样?

#include <array>
const std::array<char, 5> hello = { 'h', 'e', 'l', 'l', 'o' };

#include <cstdio>
int main()
{
    return std::printf("%.*s\n", hello.size(), &hello.front());
}

如果你没有C ++ 11,Boost.Array会起作用,或者你可以自己动手。 请注意,这只是const char[5]周围的类型包装,所以应该可以进入数据段(我已经确认它使用我的本地gcc进入.rodata。)

答案 1 :(得分:2)

我实际上忘记了这个Q,我不知道是否可以找到当时正在使用的原始代码,但是我已经弄清楚了如何在不终止NUL字符的情况下存储字符串。

在c ++ 17中,我能够用不包含尾随零的字符串填充constexpr std::array<char, n>

#include <array>
#include <cstdio>

constexpr size_t str_len(char const * x)
{
    char const * begin = x;
    while (*x) {
        ++x;
    }
    return x - begin;
}

constexpr auto var = "hello there";

template <size_t I, size_t Max>
constexpr auto fn()
{
    // Although I did this recursively, this could have also been done iteratively.
    if constexpr (I < Max) {
        auto x = fn<I + 1, Max>();
        x[I] = var[I];
        return x;
    }
    else {
        return std::array<char, Max>{};
    }
}

int main()
{
    auto x = fn<0, str_len(var)>();
    printf("'%*.*s'\n", x.size(), x.size(), x.data());
    return 0;
}

这将进行以下汇编:

.LC0:
  .string "'%*.*s'\n"
main:
  sub rsp, 24
  mov edx, 11
  mov esi, 11
  movabs rax, 7526676540175443304 ; <<< hello there
  mov QWORD PTR [rsp+5], rax
  mov eax, 29285
  lea rcx, [rsp+5]
  mov edi, OFFSET FLAT:.LC0
  mov WORD PTR [rsp+13], ax
  xor eax, eax
  mov BYTE PTR [rsp+15], 101
  call printf
  xor eax, eax
  add rsp, 24
  ret

是的,7526676540175443304是“你好”,没有任何终止NUL字符。 ? 参见 Demo

main()中的第一行放入全局空间将导致该字符串位于全局.text段中。

.LC0:
  .string "'%*.*s'\n"
main:
  sub rsp, 8
  mov ecx, OFFSET FLAT:x
  mov edx, 11
  xor eax, eax
  mov esi, 11
  mov edi, OFFSET FLAT:.LC0
  call printf
  xor eax, eax
  add rsp, 8
  ret
x:           ; <<< hello there
  .byte 104
  .byte 101
  .byte 108
  .byte 108
  .byte 111
  .byte 32
  .byte 116
  .byte 104
  .byte 101
  .byte 114
  .byte 101

Demo

我也可以将其放入类型中:

template <char x, typename...Ts>
struct X
{
};

constexpr int str_len(char const * x)
{
    char const * begin = x;
    while (*x) {
        ++x;
    }
    return x - begin;
}

constexpr auto var = "hello there";

template <int I>
constexpr auto fn()
{
    if constexpr (I - 1 != 0)
        return X<var[str_len(var) - I], decltype(fn<I - 1>())>{};
    else
        return X<var[str_len(var) - I], void>{};
}

int main()
{
    decltype(nullptr)(fn<str_len(var)>());
    return 0;
}

哪个给我输出:

<source>:28:5: error: cannot convert 'X<'h', X<'e', X<'l', X<'l', X<'o', X<' ', X<'t', X<'h', X<'e', X<'r', X<'e', void> > > > > > > > > > >' to 'decltype(nullptr)' (aka 'nullptr_t') without a conversion operator
    decltype(nullptr)(fn<str_len(var)>());
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Demo

现在,我可以更加随意地按摩它,使其进入上面要求的状态。要求是将字符串存储为不以NULL结尾的字符串,也要在c ++ 0x中执行此操作,但这不是,所以我不会将其标记为答案。但我想我会把它放在那里。

编辑

似乎gnu和clang也具有扩展名,可以将字符串放入模板类型:

template <char...Cs>
struct chars {};

template <typename T, T...Xs>
chars<Xs...> operator""_xxx() {
    return {};
}

int main()
{
    decltype(nullptr)("hello there"_xxx);
    return 0;
}

哪个吐出来:

<source>:5:14: warning: string literal operator templates are a GNU extension [-Wgnu-string-literal-operator-template]
chars<Xs...> operator""_xxx() {
             ^
<source>:11:5: error: cannot convert 'chars<'h', 'e', 'l', 'l', 'o', ' ', 't', 'h', 'e', 'r', 'e'>' to 'decltype(nullptr)' (aka 'nullptr_t') without a conversion operator
    decltype(nullptr)("hello there"_xxx);
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Demo

请注意,我现在想到的唯一将字符串放入模板参数的原因是将字符串作为constexpr进行传输,这可能有一些有趣的原因,例如允许对所述constexpr函数的返回类型基于传递的字符串。这有一些有趣的可能性。

附加说明:不可能将字符串直接传递给constexpr函数并使其变型返回类型,因为作为参数,它不再是constexpr,这有点烦人。 操作constexpr字符串并修饰返回类型的唯一方法是在函数外部将其声明为constexpr,然后从中引用该外部constexpr变量。在函数中,如我的第二个示例所示。

编辑2

结果表明,尽管您不能直接将某些内容作为constexpr值传递,但是可以传递一个lambda来用作constexpr函数。

#include <array>
#include <cstdio>

constexpr size_t str_len(char const * x)
{
    char const * begin = x;
    while (*x) {
        ++x;
    }
    return x - begin;
}

template <size_t I = 0, typename FN>
constexpr auto fn2(FN str) {
    constexpr auto Max = str_len(str());
    if constexpr (I < Max) {
        auto x = fn2<I + 1>(str);
        x[I] = str()[I];
        return x;
    }
    else {
        return std::array<char, Max>{};
    }
}

auto x = fn2<>([]{ return "hello there"; });

int main()
{
    printf("'%*.*s'\n", x.size(), x.size(), x.data());
    return 0;
}

与我的第一个示例产生相同的asm输出。 Demo

坦率的说,它确实有效。

编辑3

鉴于我已经弄清楚了如何传递constexpr字符串,所以我现在可以创建一个非递归类型:

#include <utility>

constexpr std::size_t str_len(char const * x)
{
    char const * begin = x;
    while (*x) {
        ++x;
    }
    return x - begin;
}

template <char...> struct c{};

template <typename FN, std::size_t...Is>
constexpr auto string_to_type_impl(FN str, std::index_sequence<Is...>)
{
    return c<str()[Is]...>{};
}

template <typename FN>
constexpr auto string_to_type(FN str)
{
    constexpr auto Max = str_len(str());
    return string_to_type_impl(str, std::make_index_sequence<Max>{});
}

int main()
{
    std::nullptr_t(string_to_type([]{ return "hello there"; }));
    return 0;
}

输出结果:

<source>:29:5: error: cannot convert 'c<'h', 'e', 'l', 'l', 'o', ' ', 't', 'h', 'e', 'r', 'e'>' to 'std::nullptr_t' (aka 'nullptr_t') without a conversion operator
    std::nullptr_t(string_to_type([]{ return "hello there"; }));
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.

Demo

当然,对于这些与c ++ 11一起使用的功能,constexpr函数必须转换为递归三元版本。