【问题标题】:C string size on stack from different c string array来自不同c字符串数组的堆栈上的C字符串大小
【发布时间】:2021-12-01 15:25:15
【问题描述】:

我想通过 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 在堆栈上分配 300 字节在大多数微控制器系统中是不允许的,即使是高端系统也是如此。如果您需要那么多,您可以将其分配为 static 中的 .bss
  • 我现在添加了嵌入标签。微控制器是一种具有内置硬件外围设备的嵌入式系统 CPU。它不是一个从事金融工作的小人物,也不是个人电脑。因此,答案需要适合微控制器嵌入式系统。
  • 更好的解决方案可能是不要尝试在内存中组装整个字符串,而只是将其逐步输出到串行输出流。如果您的串行 I/O 被缓冲,那么您将有效地使用现有的缓冲区来组装字符串。
  • @Clifford 如果它使用 DMA,那么可能需要提前(在 DMA 缓冲区中)一次写下所有内容。

标签: c string embedded microcontroller


【解决方案1】:

通常应该避免在堆栈上使用可变大小的对象。首先,字符串的大小可能会增长太多而无法保存在堆栈中。第二,使用自动 VLA 或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。根本不需要在运行时执行。
【解决方案2】:

首先,“但我希望它是动态计算的”没有多大意义,因为您的数组必须足够大以处理最坏的情况。在最坏的情况下,您的代码不会因堆栈溢出而跑到树林中,因此您可以简单地为该数组分配足够大的数组。

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


使用结构数组代替字符串数组:

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"),

【讨论】:

    【解决方案3】:

    如果你的编译器支持 VLA,你可以使用这个:

    #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:工程师的时间几乎总是比几个周期贵得多。
    • @TomV 除非您不知道如何在闪存中分配一堆简单的查找表,否则您可能不是(软件)工程师......在这种情况下,它正在构建一个输出缓冲区到 UART,因此代码可能需要及时执行,理想情况下比发送具有给定波特率的字节所需的时间更长。
    猜你喜欢
    • 1970-01-01
    • 2018-02-16
    • 2014-04-24
    • 1970-01-01
    • 2012-08-09
    • 2016-03-05
    • 1970-01-01
    • 2015-09-27
    • 2023-03-07
    相关资源
    最近更新 更多