在 AVR 的程序存储器中构建编译时任意长度数组

Posted

技术标签:

【中文标题】在 AVR 的程序存储器中构建编译时任意长度数组【英文标题】:Building compile-time arbitrary length arrays in program memory for AVR 【发布时间】:2017-09-30 15:29:30 【问题描述】:

我正在尝试找出一种聪明的方法来为 AVR 架构构建一个复合编译时数组。数组的结构应该如下:

它应该完全驻留在程序内存中; 它由一系列连续的(无符号)字节组成,又名uint8_t; 它应该使用任意长度的字节段来构建; 一个段依次由一个长度字节和一系列数据字节组成,其中长度字节为数据字节数。

这是一个这样的数组的例子:

static const uint8_t data[] PROGMEM = 
    1, 0x01,
    3, 0xBE, 0x02, 0x00,
    3, 0x3D, 0x33, 0x33,
    15, 0xE1, 0xD0, 0x00, 0x05, 0x0D, 0x0C, 0x06, 0x2D, 0x44, 0x40, 0x0E, 0x1C, 0x18, 0x16, 0x19,
    0 /* end of the sequence */
;

我想避免每次在序列中添加或删除字节时调整长度字节的负担,例如,以某种形式的伪代码:

BEGINNING_OF_THE_SEQUENCE(identifier)
    SEGMENT(0x01),
    SEGMENT(0xBE, 0x02, 0x00),
    ...
END_OF_THE_SEQUENCE()

在上面的示例中,我选择了显式字节数组声明,但它可以以任何方式构建,例如使用结构。唯一的前提是必须保证出现的顺序。

所以简而言之,我想“连接”一系列字节,其长度必须在编译时计算并放在每个字节系列的前面作为系列本身的长度字节。

我考虑过使用variadic macros,但我也想研究其他方法,例如类和函数模板、元编程等,考虑到最小的代码。我也不想诉诸 C++11 的具体细节,因为目前我正在使用的 avr-gcc 编译器的支持是有限的。

我有一种预感,可以使用模板,但我被卡住了。有什么想法吗?

【问题讨论】:

您不能使用运行时机制来更改编译时结构。 它可以通过一些聪明的方式完成:P 没有可变参数模板和 constexpr 这似乎不现实,而使用更新的 C++ 标准这绝对是可能的。 C != C++。使用您实际使用的语言进行标记。 @Nasha:我相应地更新了我的答案。 【参考方案1】:

以下是 C++11 及更高版本的简单示例,可能会有所帮助:

template <typename ...Args>
constexpr std::size_t n_args(Args...)  return sizeof...(Args); 

#define ELEM(...) n_args(__VA_ARGS__), __VA_ARGS__

#include <iostream>

int main()

    unsigned int a[] =  ELEM(4, 9, 16),
                         ELEM(100),
                         ELEM(10, 20, 30, 40, 50),
    ;

    for (auto n : a ) std::cout << n << " ";
    std::cout << '\n';

或者,如果您想要 C99 解决方案而不是 C++11 解决方案,您可以使用复合字符数组文字 sizeof 代替 n_args

#define ELEM(...) sizeof((char[])__VA_ARGS__), __VA_ARGS__

我不知道有一种类似的简单方法可以在 C++03 中使用。

【讨论】:

没关系,我用constexpr检查了你的建议,令人惊讶的是,avr-gcc 6.4 可以正确编译,将所有内容都放入程序内存中。感谢您指出这一点! 顺便说一句,我只是想知道......是否可以使用模板而不是混合模板和宏来构建整个......东西(仍在编译时)? @Nasha 是的,有可能,你应该在下面查看我的答案。 @Nasha:是的,但要注意编译时间。我不知道您的应用程序域有多大,但无限递归可变参数模板不一定便宜。我理解并完全同情您对宏的立场,但我会在这里做出务实的权衡以找到合适的解决方案。它还很大程度上取决于您是否需要将其仅放在一个源文件中或作为可重用库,是否可以为其找到一个好的、唯一的名称等。在抽屉中记录可能的替代方案。 感谢您的见解@KerrekSB。我怀疑编译时间会增加——毕竟递归必须以某种方式花费时间,如果不是在运行时,那么它将在编译时,这是合乎逻辑的。这就是我将您的答案标记为解决方案的原因。 (也因为我仍在尝试了解模板化解决方案的工作原理。)【参考方案2】:

这是一种方法:

#include <stdint.h>
#include <stdio.h>
#define PROGMEM
#define _ARGSIZE(...) sizeof((uint8_t[])__VA_ARGS__)/sizeof(uint8_t)
#define _SEGMENT(...)  _ARGSIZE( __VA_ARGS__ ), __VA_ARGS__
#define BEGINNING_OF_THE_SEQUENCE(__id)   uint8_t static const __id[] PROGMEM =  
#define END_OF_THE_SEQUENCE() 

BEGINNING_OF_THE_SEQUENCE(data)
    _SEGMENT(0x01),
    _SEGMENT(0xBE, 0x02, 0x00),
    _SEGMENT(0xDE, 0xAD, 0xBE, 0xEF)
END_OF_THE_SEQUENCE();

int main() 
    int k, counter = data[0];
    for (k = 0; k < sizeof(data); k++) 
        fprintf(stderr, "%02x ", data[k]);
        if(counter-- == 0) 
            counter = data[1+k];
            fprintf(stderr, "\n");
        
    

这种方法与 C99 兼容。

上面的宏可以修改,以处理传递的任何类型的数据结构,而不是 uint8_t(包括结构中的 :P 结构)

【讨论】:

谢谢。在发布我的消息之前,我写了类似的东西。它在 C 和 C++ 中都能完美运行。【参考方案3】:

无宏 C++11 方法 (v2):

#include <array>
#include <iostream>
#include <cstddef>
#include <cstdint>

//  This template will be instantiated repeatedly with VItems list
//  populated with new items.
template<typename TItem,  TItem... VItems> class
t_PackImpl

    //  This template will be selected for second and all other blocks.
    public: template<TItem... VInnerItems> using
    t_Pack = t_PackImpl
    <
        TItem
    //  add all previous items
    ,   VItems...
    //  add item holding amount of items in new block
    ,   TItemstatic_cast<TItem>(sizeof...(VInnerItems))
    //  add new block items
    ,   VInnerItems...
    >;

    //  This method will be called on the last instantiated
    //  template with VItems containing all the items.
    //  Returns array containing all the items with extra 0 item at the end.
    public: static constexpr auto
    to_array(void) -> ::std::array<TItem, sizeof...(VItems) + ::std::size_t1>
    
        return VItems..., TItem;
    
;

//  This template will be instantiated just once.
//  Starts t_PackImpl instantiation chain.
template<typename TItem> class
t_BeginPack

    //  This template will be selected for first block.
    public: template<TItem... VInnerItems> using
    t_Pack = t_PackImpl
    <
        TItem
    //  add item holding amount of items in new block
    ,   TItemstatic_cast<TItem>(sizeof...(VInnerItems))
    //  add new block items
    ,   VInnerItems...
    >;
;

int main()

    
        constexpr auto items
        
            t_BeginPack<::std::uint8_t>::t_Pack<42>::to_array()
        ;
        for(auto const & item: items)
        
            ::std::cout << static_cast<::std::uint32_t>(item) << ::std::endl;
        
    
    ::std::cout << "----------------" << ::std::endl;
    
        constexpr auto items
        
            t_BeginPack<::std::uint8_t>::t_Pack<0, 1, 2>::to_array()
        ;
        for(auto const & item: items)
        
            ::std::cout << static_cast<::std::uint32_t>(item) << ::std::endl;
        
    
    ::std::cout << "----------------" << ::std::endl;
    
        constexpr auto items
        
            t_BeginPack<::std::uint8_t>::
                t_Pack<0, 1, 2>::
                t_Pack<0, 1>::
                t_Pack<0, 1, 2, 3, 4, 5>::to_array()
        ;
        for(auto const & item: items)
        
            ::std::cout << static_cast<::std::uint32_t>(item) << ::std::endl;
        
    
    return(0);

Run online

【讨论】:

我猜你的答案涉及元编程,对吧? @Nasha 很明显,因为它涉及模板。 我倾向于在我的 mycrocontroller 项目中避免使用 STL,也就是说。另外,我不完全理解您的答案,尤其是 using 关键字代表什么。事实上,构造细节和 C++ 编程的那部分对我来说仍然是模糊的。我天真地认为我很容易理解,但我没有。你介意添加一个关于这一切的详细解释吗?抱歉,我在那里感觉自己像个菜鸟…… @Nasha 实际上给出的方法并不那么依赖标准库,我只使用了整数类型(uint8_tsize_t)和std::array 包装器(可以用普通替换几乎没有修改的 C 样式数组)。 using 关键字用于声明alias templates。每个班级内的t_Pack 模板都会实例化t_PackImpl 模板,并在VItems 中添加新项目。 好的。现在我需要to_array() 方法吗?我仍然需要弄清楚如何将整个字节数组 static const 放入程序内存部分。

以上是关于在 AVR 的程序存储器中构建编译时任意长度数组的主要内容,如果未能解决你的问题,请参考以下文章

单片机成长之路(avr基础篇)- 003 AVR单片机的BOOT区

Collection

动态数组

编译的 AVR 程序集二进制文件似乎不包含我的说明?

java反射

AVR_GCC 编译错误 delay.h