Forums

如何隐藏多毛的全局变量?

开始于 麦克斯菲尔德 7 months ago12条回复最新回复7个月前77浏览

您好,很抱歉给您带来另一个愚蠢的编码问题(我是硬件设计工程师)。这与我的12x12乒乓球阵列有关,其中每个球都包含一个三色LED(//www.clivemaxfield.com/awesome-12-x-12-ping...)

有人建议我使用数组滚动文本消息,所以我想给它一个旋转。我正在使用Arduino IDE。作为其中的一部分,我有一个以0和1组成的三维数组形式的全局变量,该数组定义了用于构建字符的7x5点模式:

uint8_t CharTable [NUM_ASCII_CHARS] [NUM_CHAR_ROWS] [NUM_CHAR_COLS] =
{
    // Lots of 0s and 1s
};

问题在于此数组的内容总计约550行代码。我的第一个想法就是简单地将此声明粘贴在程序的末尾,但是随后我收到一条警告消息,提示我尝试在函数中访问它时无法识别。

如果将声明移到函数调用上方的程序顶部,没有问题。我在这里有点困惑,因为-尽管通常按照惯例在main()之前声明全局变量-我认为C / C ++多遍编译器允许将它们声明为代码中的任何地方(函数之外) 。

接下来,我尝试将声明(如上所示)保留在程序顶部,但将主体(上面表示“ //很多0和1s”的地方)替换为#include,如下所示:

uint8_t CharTable [NUM_ASCII_CHARS] [NUM_CHAR_ROWS] [NUM_CHAR_COLS] =
{
#include“ AcsiiTable.txt”
};

我将文本文件放在草图的同一文件夹中,但是编译器说“找不到文件”

那么,对于处理此问题的最佳方法,您有什么建议吗?

一如既往的感谢-最大(1/2人,1/2野兽,1/2机智)




[-]
回覆者 霍奇奇2020年7月13日

编译器需要一点帮助,这可以通过声明前向声明来完成。在C语言中,这是使用外部关键字完成的,如下所示:

extern uint8_t CharTable [NUM_ASCII_CHARS] [NUM_CHAR_ROWS] [NUM_CHAR_COLS];

请确保在上述声明之前声明了NUM_ASCII_CHARS,NUM_CHAR_ROWS,NUM_CHAR_COLS。

将以上语句放在程序顶部,然后可以在底部声明数组。但是,我建议将其放入单独的.c文件中。如果Arduino IDE 可以支持多个文件。在这种情况下,我将创建两个文件,分别是array.c和array.h。将extern声明放置在array.h中,将实际的数组放置在array.c中,然后将array.h包含在程序中,确保 Arduino的 IDE正在合并array.c文件以及您的程序文件。


[-]
回覆者 麦克斯菲尔德2020年7月13日

真棒!!!我只是尝试使用“外部”方法,它就像一种魅力/每天我都学到新东西-谢谢您的明智建议!!!

[-]
回覆者 科奇尼亚2020年7月13日

除了“ hodgec”已经告诉您的“外部”业务外,还有两条评论。

1.您正在浪费空间。您将1位存储在一个字节中。您可以将7行的所有位存储在一个字节中,并使用按位运算符来获取这些位。这样可以节省大量空间;一个仅使用5个字节而不是35个字节的字符。

2.您未在声明中使用'const'关键字。这迫使编译器将您的内容放入RAM,因为它应该是可修改的数据。因此,您将丢失FLASH空间(存储初始化数据的空间)和RAM(在启动时将初始化数据复制到的空间)。

在您当前的项目中,这可能并不重要,但是如果您要使用对成本敏感的裸机嵌入式系统,那么您将使用资源受限的MCU,而这些都是问题所在。当然,还有其他考虑因素(例如速度,因为RAM通常比FLASH存取速度更快),但了解数据存储在何处以及需要多少空间很有用。

[-]
回覆者 麦克斯菲尔德2020年7月13日

我一直在想(别笑;这是真的)。您是否同意:

-对于创建要在PC等高级系统上运行的应用程序的软件开发人员而言,关键字const应用于变量仅表示无法在软件控制下更改变量。

-对于嵌入式系统的开发人员,它具有附加含义,即该变量将存储在闪存中而不是SRAM中。

你说什么

[-]
回覆者 科奇尼亚2020年7月13日

好吧,const关键字有点误导人。如果您阅读了C标准,事实证明const关键字的含义是,不多于,不少于:

“我向编译器保证不会在const关键字适用的范围内更改此变量。”

这并不意味着该变量是一个常数。实际上,在嵌入式系统中,您会发现以下内容:

extern volatile const uint32_t某些内容;

如果同时使用volatile和const关键字,则表明程序员很疯狂,而实际上“ something”是只读硬件寄存器,程序不应(也可能不能)更改,但是底层硬件可以,并且做。

编译器有权忽略const关键字。它可以考虑关键字并在更改const对象时发出警告,但实际上并不能阻止您这样做:

const int x = 1;

*(int *)&x = 3;

当您使用显式强制类型转换告诉编译器您知道自己在做什么时,它将在没有警告的情况下进行编译。当然,如果编译器在闪存中放置了“ x”,那么您的程序将在运行时失败,但这是另一个问题。

但是,由于您向编译器保证不会更改x,因此它有权将x放入FLASH中。或者,在台式机系统上,它可以将其放入代码段中并在该段上设置写保护属性,在这种情况下,如果尝试绕过编译器的后台并偷偷地写入x,则代码将崩溃。

Gcc通常将您的const声明的初始化数据放入一个名为.cdata的段中。实际上,取决于您的链接描述文件是否将其放入闪存中。但是,它可能不是那么简单,因为对于某些哈佛体系结构(例如AVR内核),FLASH和RAM并未映射到统​​一的地址空间,并且您不能仅以与FLASH和RAM相同的方式访问FLASH。内存。因此,这不是一个明确的问题,您需要知道自己在做什么。

但是,如果您不写任何东西,那么使用const关键字总是值得的,因为它可能有助于编译器优化代码和/或节省空间。

[-]
回覆者 麦克斯菲尔德2020年7月13日
您好Kocsonya-非常感谢您的反馈。提出您的观点-我将按照相反的顺序:

2. 这确实是一个好点-正如您所说,在以下方面可能并不重要 以我目前的项目为例,但我真的应该养成 以正确的方式做事。而且,老实说,我没有意识到 使用关键字const只会将事物锁定在( 当然,现在您提到它就很有意义了。

一个问题-我是否必须在前面的extern语句中使用const关键字:

const extern uint8_t CharTable [NUM_ASCII_CHARS] [NUM_CHAR_ROWS] [NUM_CHAR_COLS];

还是在程序末尾附上完整的声明?

1. 我同意我在浪费空间,但是我以此为例 初学者比我对C有更少的了解(信不信由你)。 以下是我的3D数组中与ASCII字符关联的行 for the number 0: 

        {
            {0,1,1,1,0},// 48,0x30,'0'
            {1,0,0,0,1},
            {1,0,0,1,1},
            {1,0,1,0,1},
            {1,1,0,0,1},
            {1,0,0,0,1},
            {0,1,1,1,0}
        },

正在做 这是他们可以全神贯注的东西-也是 向东让他们调整角色并添加自己的角色。如果允许C 您以二进制形式指定变量值,这就是我要的方式 没了不幸的是,我只有十进制或十六进制,并且 将字符指定为七个十六进制值 我们所做的事情更加晦涩难懂。但是,我会在我的笔记中添加 文章解释了为什么我这样做,为什么会更多 高效地按自己的方式做。
[-]
回覆者 科奇尼亚2020年7月14日

相反,extern const。顺序很重要;如果使用const extern,编译器将抱怨。关键字extern,register,static和auto是存储类说明符,它们必须排在第一位。 const,volatile和strict(以及C11和_Atomic)关键字是类型限定符,它们在声明中的位置会影响其含义:

字符* x; //可变X指向可变字符

const char * x; //可变的X指向常量char

char * const x; // X是常数,指向可变的char

const char * const x; // X是一个常数,指向常数char

根据二进制数字,如果您使用基于gcc的开发系统,则可以使用gcc扩展名来指定二进制常量:

a = 0x85;

a = 0b10000101;

和gcc扩展默认情况下处于启用状态,只有在您明确要求遵循特定的C标准时,它们才会被禁用,甚至在某些情况下,如果您还告诉编译器对标准不屑一顾,则可能会保持启用状态。


[-]
回覆者 麦克斯菲尔德2020年7月14日

非常感谢您抽出宝贵的时间来解释这些内容。只是一个问题(现在大声笑)

在使用数组之前,是否必须在程序的开头同时使用extern和const:

//程序开始
extern const uint8_t CharTable [NUM_ASCII_CHARS] [NUM_CHAR_ROWS] [NUM_CHAR_COLS];

//函数和东西在这里


然后在我正式声明并填充数组时在最后使用它们:

//程序结束
extern const uint8_t CharTable [NUM_ASCII_CHARS] [NUM_CHAR_ROWS] [NUM_CHAR_COLS] =

{
    //
};


或者我可以从一开始就错过他们中的一个或两个吗(如果可以的话,我应该吗?)

[-]
回覆者 manoweb2020年7月15日

如果您使用的是Arduino,我相信您必须使用的是C ++,而不是C(支持...)(支持0b二进制表示法)。

但是,我不会太担心让您的听众接触“二进制到十六进制”“编码”。我记得小时候读过如何为VIC-II芯片创建子画面,然后我花了整个夏天在方格纸上画出子画面,然后在海滩上那些无聊的日子里用手工将它们编码为十进制。有一些学习曲线,但是我相信这种回报是值得的。

但是请记住什么用户 科奇尼亚说,仅声明一个const并确保生成的工件仅保留闪存中的副本通常是“不够的”。在Arduino中,通常必须使用F()宏,但并非在所有情况下都必须使用。

[-]
回覆者 麦克斯菲尔德2020年7月15日

嗨Manoweb-感谢您的反馈-一方面我很想回去并将所有内容更改为二进制字符串-另一方面,我有剩余的内存,并且我已经将数组创建为8-位整数(有一个下午我不会再见到大声笑)

我还没有收到Kocsonya的回覆,涉及以下问题(也许我已经厌倦了他/她)-您可以对此进行任何说明:


在使用数组之前,是否必须在程序的开头同时使用extern和const:

//程序开始
extern const uint8_t CharTable [NUM_ASCII_CHARS] [NUM_CHAR_ROWS] [NUM_CHAR_COLS];

//函数和东西在这里

然后在我正式声明并填充数组时在最后使用它们:

//程序结束
extern const uint8_t CharTable [NUM_ASCII_CHARS] [NUM_CHAR_ROWS] [NUM_CHAR_COLS] =

{
    //
};


或者我可以从一开始就错过他们中的一个或两个(如果可以的话,我应该吗?
[-]
回覆者 manoweb2020年7月15日

当将extern应用于变量时,表示“已声明,但在其他位置定义”。因此,在程序开始时,这就是您应该拥有的。在程序结束时,即使您提供了定义,编译器仍然可以允许您使用`extern`,但是我认为这不是很好的做法,并且我认为这样做会损害可读性。

关于`const`说明符。当然,我不希望这个概念与`const`不匹配,所以我将它们都保留为`const`。如果不这样做会发生什么,取决于...。我有一个主意,但我需要做更多的研究才能确认。在任何情况下都不太可能通过代码审查:)

[-]
回覆者 麦克斯菲尔德2020年7月15日

很棒-非常感谢您的帮助-Max