不同 C 字符串数组在堆栈上的 C 字符串大小

3

我想通过UART发送字符串来调试我的C微控制器程序。 现在我遇到了一个问题,即如何将调试字符串连接在一起,因为我必须使用c字符串。 我需要在堆栈上有debugMessageOnStack

考虑以下示例: 我需要一个像“我的最爱颜色是山茶玫瑰色”这样的字符串。

const char debugMessageStart[] = "My favorite color is ";
const char* colors[MAX_COLORS] = {[GREEN] = "green", [ORANGE] = "orange", [PINK] = "mountbatten pink"};

char debugMessageOnStack[DEBUG_MESSAGE_LENGTH];

我知道手动计算DEBUG_MESSAGE_LENGTH相当容易,但我希望能够动态计算它,这样每次添加一个比所有已有颜色字符串都长的新颜色字符串时,我就不必手动重新计算它。

理论上讲,由于编译前会知道colors中每个字符串的长度,因此应该是可能的,但我不知道该如何实现。

谢谢!


简单的方法:如果您不是真正缺少堆栈空间,那么您应该只声明char debugMessageOnStack[DEBUG_MESSAGE_LENGTH];,其中DEBUG_MESSAGE_LENGTH比任何消息都要长,例如300,这应该足够了。 - Jabberwocky
@Jabberwocky 在大多数微控制器系统中,甚至是高端的系统中,在栈上分配300字节是不可取的。如果您需要这么多空间,可以将其作为.bss中的静态变量进行分配。 - Lundin
我现在已添加了嵌入式标签。微控制器是一种带有内建硬件外围设备的嵌入式系统CPU。它不是一个从事金融工作的小家伙,也不是个人电脑。因此,答案需要适用于微控制器嵌入式系统。 - Lundin
1
一个更好的解决方案可能是不要尝试在内存中组装整个字符串,而是逐步将其输出到串行输出流。如果您的串行I/O是缓冲的,那么您将有效地使用现有缓冲区来组装字符串。 - Clifford
@Clifford 如果使用DMA,则可能需要提前一次性将所有内容写入(在DMA缓冲区中)。 - Lundin
@Lundin 是的 - 这可能是 - 因此存在矛盾。但即使如此,您也可以(并且我认为应该)从环形缓冲区或字节队列中提供DMA缓冲区的数据。这只是另一层抽象 - 应用程序层最好隐藏底层硬件机制。 - Clifford
3个回答

3
首先,“但我希望它能够动态计算”这个说法并不是很有意义,因为您的数组必须足够大以处理最坏情况。在最坏情况下,您的代码不能发生栈溢出,因此您可以简单地为其分配足够大的数组。

当不处于最坏情况时,“节省一些堆栈空间”是没有意义的,因为这只是一个本地变量,并且无论大小如何,在使用后都会被释放。


使用结构体数组而不是字符串数组:

typedef struct
{
  size_t length;
  const char* str;
} color_t;

const color_t colors[] =
{
  [GREEN] = { sizeof("green")-1, "green" },
  ...
};

这样所有内容都存储在闪存中并在编译时计算。如果您非常注重减少代码重复,则可以添加一个神秘的宏(通常不建议):

#define COLOR_INIT(str) { sizeof(str)-1, str }
...
[GREEN] = COLOR_INIT("green"),

2
通常情况下,应该避免在堆栈上使用变量大小的对象。首先,字符串的大小可能会增长到无法保留在堆栈上的程度。其次,使用自动变长数组(automatic VLAs)或alloca()进行堆栈分配通常比固定大小对象要低效(尽管仍然比malloc()更快)。
无论如何,在堆栈上处理未知大小的字符串,可以使用snprintf函数。该函数类似于printf(),但它将字符存储到字符串中。然而,如果将NULL作为缓冲区并将0作为大小传递,则返回将存储到缓冲区中的字符数。
字符串将通过两个步骤生成:
size_t n = snprintf(NULL, 0, "My favorite color is %s", colors[ORANGE]);
char msg[n + 1]; // 1 for the terminator
snprintf(msg, n + 1, "My favorite color is %s", colors[ORANGE]);

将消息生成包装成一个助手函数可能是个好主意,以避免重复:

size_t mk_msg(char *msg, size_t n, int color) {
  return snprintf(msg, n, "My favorite color is %s", colors[color]);
}

这种方法的优点是非常通用和可重用。

在嵌入式系统中,通常应避免使用stdio.h函数,因为它们效率低下且占用大量内存。此外,这些函数通常具有可怕、易出错和危险的API问题。根本没有必要在运行时执行此操作。 - Lundin

1
如果您的编译器支持可变长度数组,则可以使用以下内容:
#include <stdio.h>
#include <string.h>

#define MAX_COLORS 3
#define GREEN 0
#define ORANGE 1
#define PINK 2

const char debugMessageStart[] = "My favorite color is ";
const char* colors[] = { [GREEN] = "green",[ORANGE] = "orange", [PINK] = "mountbatten pink" };

int main(void) 
{
  const char* color = colors[ORANGE];
  int length = strlen(debugMessageStart) + strlen(color) + 1;
  char debugMessageOnStack[length];
  strcpy(debugMessageOnStack, debugMessageStart);
  strcat(debugMessageOnStack, color);
  printf("%s\n", debugMessageOnStack);
}


这种方法非常低效。对于嵌入式系统来说,实时性能通常是最有价值的资源。 - Lundin
@Lundin:工程师的时间几乎总是比几个周期更昂贵。 - Tom V
1
@TomV 除非你不知道如何在闪存中分配一堆简单的查找表,否则你可能不是一个(软件)工程师... 在这种情况下,它正在构建一个输出缓冲区到UART,因此代码可能需要及时执行,最好比使用给定波特率发送字节所需的时间更长。 - Lundin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接