硬汉嵌入式论坛

 找回密码
 立即注册
查看: 1262|回复: 12
收起左侧

[MDK] 递归函数里调用 printf ,sprintf 等函数会死机?

[复制链接]

58

主题

267

回帖

446

积分

高级会员

积分
446
发表于 2022-1-25 17:06:34 | 显示全部楼层 |阅读模式
本帖最后由 ihavedone 于 2022-1-25 17:12 编辑

遇到个问题,我有个需求是打印节点树,写了个函数,如果节点有子节点就递归函数自身,
没有子节点就循环下一个节点。  直到最终打印完成。

现在遇到个问题是,如果不在函数里调用 printf ,sprintf, snprintf 这些函数,就没事。一旦调用了就会死机。。。
但是如果调用了上面的打印函数,但是调试模式下,一步一步跟踪调试,它就不会死机。。。

单步调试的时候打印了一个局部变量的地址,每次递归都在堆栈范围内,没有溢出。

其他非递归函数调用打印函数是没问题的。  

很奇怪。



回复

使用道具 举报

58

主题

267

回帖

446

积分

高级会员

积分
446
 楼主| 发表于 2022-1-29 23:18:33 | 显示全部楼层
本帖最后由 ihavedone 于 2022-1-29 23:21 编辑

问题最终解决了。 不是堆栈溢出。
是我自己实现了 malloc,realloc,free 和 calloc 函数。
malloc 实现方式是通过 cmsis_os v2  的 osMemoryPool 功能来分配 pool,然后 从 pool 里分配内存。
free 的时候,先 释放内存,然后删除 pool。。  这里忘记先释放内存了,直接删除了 pool,就会出问题。。。


回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106997
QQ
发表于 2022-1-25 17:37:22 | 显示全部楼层
方便的话,分享一个你递归测试有问题的代码,代码越简单也好,最好递归里面只有个sprintf。

然后我帮你也测测看
回复

使用道具 举报

210

主题

1045

回帖

1685

积分

至尊会员

More we do, more we can do.

积分
1685
发表于 2022-1-25 17:49:41 | 显示全部楼层
解决过类似的问题,仿真时的栈深度和运行时是有差异的。
回复

使用道具 举报

58

主题

267

回帖

446

积分

高级会员

积分
446
 楼主| 发表于 2022-1-25 18:04:08 | 显示全部楼层
本帖最后由 ihavedone 于 2022-1-25 18:08 编辑
eric2013 发表于 2022-1-25 17:37
方便的话,分享一个你递归测试有问题的代码,代码越简单也好,最好递归里面只有个sprintf。

然后我帮你 ...

做了一些实验,发现在递归函数里调用简单的 printf("123"),或者 sprintf(buf, "123"); 等并不会死机。

但是当命令格式串越来越复杂,参数越来越多后,就会死机了。。。

采用暴力方法,加堆栈,16k 加到 32k,并且屏蔽几个 sprintf,结果可以了。。。
然后把屏蔽的几个打开,死机了。
然后继续加堆栈到 64k,可以了。。。

比较不理解的是: sprintf 中的 buf 是函数传进去的地址。
每次递归,都会从外层函数的结尾处开始写,应该不要特别大的堆栈才对。。。

个人猜测是不是 sprintf 等函数会先自己开辟一个局部缓存,把数据弄好后才复制到用户给定的缓存中去。。
如果是这样,那每嵌套一次堆栈用量都是蹭蹭地往上涨。



函数代码如下:

uint32_t ptc_pc_format_xml(XML_NOTE* root, char* buf, uint32_t size, uint16_t depth)
{
    uint32_t cnt = 0;
    XML_NOTE* note;

    if((root==NULL) || (buf==NULL) || (size==0))
    {
        return 0;
    }

    DBG_VAR_ADDR("cnt", cnt);

    note = root;
    while(note)
    {
        cnt += snprintf(buf+cnt, size-cnt, "%*s<%s>", depth*2, "", note->name);
        if(note->firstchild)
        {
            cnt += snprintf(buf+cnt, size-cnt, "\n");
            cnt += ptc_pc_format_xml(note->firstchild, buf+cnt, size-cnt, depth+1);
            cnt += snprintf(buf+cnt, size-cnt, "%*s</%s>\n", depth*2, "", note->name);
        }
        else if(note->pdat)
        {
            cnt += snprintf(buf+cnt, size-cnt, "%s", note->pdat);
            cnt += snprintf(buf+cnt, size-cnt, "</%s>\n", note->name);
        }
        else
        {
            cnt += snprintf(buf+cnt, size-cnt, "</%s>\n", note->name);
        }

        note = note->next;
    }
    buf[cnt] = 0;

    return cnt;
}


XML_NOTE   是节点结构体 :typedef struct _xml_node
{
    char                name[XML_NAME_MAX_LEN+1];   // 节点名称
    char*               pdat;                       // 节点内容。如果 child 不为空,则内容必须为空

    struct _xml_node*   prev;                       // 上一个同级节点
    struct _xml_node*   next;                       // 下一个同级节点
    struct _xml_node*   parent;                     // 父节点
    struct _xml_node*   firstchild;                 // 第一个子节点
    struct _xml_node*   lastchild;                  // 最后一个子节点
}XML_NOTE;

DBG_VAR_ADDR("cnt", cnt);  是打印变量地址的宏定义。



回复

使用道具 举报

58

主题

267

回帖

446

积分

高级会员

积分
446
 楼主| 发表于 2022-1-27 15:40:01 | 显示全部楼层
eric2013 发表于 2022-1-25 17:37
方便的话,分享一个你递归测试有问题的代码,代码越简单也好,最好递归里面只有个sprintf。

然后我帮你 ...

经过上次扩大堆栈到很大暂时解决问题后,后面又出现了死机。。。

最终经过排除法,发现问题在以下代码上。


用这段代码做信息输出和 log 记录时,就会死机。 但是把 log_write 屏蔽就不会死。   log_write 是保存调试信息到 log 文件。
将上面代码中的  sync_mutex_give(SYNC_MUTEX_LOG);\  移动到   log_write  的下一行,也不会死。。。

或者将调试宏改成下面的形式,也不会死。。。  (死机问题解决后,堆栈改小到 16K 也没问题)



不是太明白这里面的具体区别,按我的理解,互斥量主要是保证 printf 的原子性,防止输出的内容被穿插。
snprintf 已经是可重入的,不需要另外保证。  在生成了 dbg_buf 的内容,且 printf 输出之后, log_write 应该可以不用互斥量保护也是能行的,但实际上会死机。
然而后面的代码把 snprintf  和 log_write  完全排除在 互斥量保护之外,却也不会死机。。。 这里就很难理解了。。

不知道大佬有何见解  
回复

使用道具 举报

58

主题

267

回帖

446

积分

高级会员

积分
446
 楼主| 发表于 2022-1-27 15:40:01 | 显示全部楼层
eric2013 发表于 2022-1-25 17:37
方便的话,分享一个你递归测试有问题的代码,代码越简单也好,最好递归里面只有个sprintf。

然后我帮你 ...

经过上次扩大堆栈到很大暂时解决问题后,后面又出现了死机。。。

最终经过排除法,发现问题在以下代码上。


用这段代码做信息输出和 log 记录时,就会死机。 但是把 log_write 屏蔽就不会死。   log_write 是保存调试信息到 log 文件。
将上面代码中的  sync_mutex_give(SYNC_MUTEX_LOG);\  移动到   log_write  的下一行,也不会死。。。

或者将调试宏改成下面的形式,也不会死。。。  (死机问题解决后,堆栈改小到 16K 也没问题)



不是太明白这里面的具体区别,按我的理解,互斥量主要是保证 printf 的原子性,防止输出的内容被穿插。
snprintf 已经是可重入的,不需要另外保证。  在生成了 dbg_buf 的内容,且 printf 输出之后, log_write 应该可以不用互斥量保护也是能行的,但实际上会死机。
然而后面的代码把 snprintf  和 log_write  完全排除在 互斥量保护之外,却也不会死机。。。 这里就很难理解了。。

不知道大佬有何见解  
回复

使用道具 举报

58

主题

267

回帖

446

积分

高级会员

积分
446
 楼主| 发表于 2022-1-27 15:40:01 | 显示全部楼层
eric2013 发表于 2022-1-25 17:37
方便的话,分享一个你递归测试有问题的代码,代码越简单也好,最好递归里面只有个sprintf。

然后我帮你 ...

经过上次扩大堆栈到很大暂时解决问题后,后面又出现了死机。。。

最终经过排除法,发现问题在以下代码上。


用这段代码做信息输出和 log 记录时,就会死机。 但是把 log_write 屏蔽就不会死。   log_write 是保存调试信息到 log 文件。
将上面代码中的  sync_mutex_give(SYNC_MUTEX_LOG);\  移动到   log_write  的下一行,也不会死。。。

或者将调试宏改成下面的形式,也不会死。。。  (死机问题解决后,堆栈改小到 16K 也没问题)



不是太明白这里面的具体区别,按我的理解,互斥量主要是保证 printf 的原子性,防止输出的内容被穿插。
snprintf 已经是可重入的,不需要另外保证。  在生成了 dbg_buf 的内容,且 printf 输出之后, log_write 应该可以不用互斥量保护也是能行的,但实际上会死机。
然而后面的代码把 snprintf  和 log_write  完全排除在 互斥量保护之外,却也不会死机。。。 这里就很难理解了。。

不知道大佬有何见解  
回复

使用道具 举报

58

主题

267

回帖

446

积分

高级会员

积分
446
 楼主| 发表于 2022-1-27 15:40:01 | 显示全部楼层
eric2013 发表于 2022-1-25 17:37
方便的话,分享一个你递归测试有问题的代码,代码越简单也好,最好递归里面只有个sprintf。

然后我帮你 ...

经过上次扩大堆栈到很大暂时解决问题后,后面又出现了死机。。。

最终经过排除法,发现问题在以下代码上。


用这段代码做信息输出和 log 记录时,就会死机。 但是把 log_write 屏蔽就不会死。   log_write 是保存调试信息到 log 文件。
将上面代码中的  sync_mutex_give(SYNC_MUTEX_LOG);\  移动到   log_write  的下一行,也不会死。。。

或者将调试宏改成下面的形式,也不会死。。。  (死机问题解决后,堆栈改小到 16K 也没问题)



不是太明白这里面的具体区别,按我的理解,互斥量主要是保证 printf 的原子性,防止输出的内容被穿插。
snprintf 已经是可重入的,不需要另外保证。  在生成了 dbg_buf 的内容,且 printf 输出之后, log_write 应该可以不用互斥量保护也是能行的,但实际上会死机。
然而后面的代码把 snprintf  和 log_write  完全排除在 互斥量保护之外,却也不会死机。。。 这里就很难理解了。。

不知道大佬有何见解  
回复

使用道具 举报

58

主题

267

回帖

446

积分

高级会员

积分
446
 楼主| 发表于 2022-1-27 15:40:01 | 显示全部楼层
eric2013 发表于 2022-1-25 17:37
方便的话,分享一个你递归测试有问题的代码,代码越简单也好,最好递归里面只有个sprintf。

然后我帮你 ...

经过上次扩大堆栈到很大暂时解决问题后,后面又出现了死机。。。

最终经过排除法,发现问题在以下代码上。


用这段代码做信息输出和 log 记录时,就会死机。 但是把 log_write 屏蔽就不会死。   log_write 是保存调试信息到 log 文件。
将上面代码中的  sync_mutex_give(SYNC_MUTEX_LOG);\  移动到   log_write  的下一行,也不会死。。。

或者将调试宏改成下面的形式,也不会死。。。  (死机问题解决后,堆栈改小到 16K 也没问题)



不是太明白这里面的具体区别,按我的理解,互斥量主要是保证 printf 的原子性,防止输出的内容被穿插。
snprintf 已经是可重入的,不需要另外保证。  在生成了 dbg_buf 的内容,且 printf 输出之后, log_write 应该可以不用互斥量保护也是能行的,但实际上会死机。
然而后面的代码把 snprintf  和 log_write  完全排除在 互斥量保护之外,却也不会死机。。。 这里就很难理解了。。

不知道大佬有何见解  
回复

使用道具 举报

58

主题

267

回帖

446

积分

高级会员

积分
446
 楼主| 发表于 2022-1-27 15:40:01 | 显示全部楼层
eric2013 发表于 2022-1-25 17:37
方便的话,分享一个你递归测试有问题的代码,代码越简单也好,最好递归里面只有个sprintf。

然后我帮你 ...

经过上次扩大堆栈到很大暂时解决问题后,后面又出现了死机。。。

最终经过排除法,发现问题在以下代码上。
84933411-6A84-4527-BD55-05D95F6CBD52.png

用这段代码做信息输出和 log 记录时,就会死机。 但是把 log_write 屏蔽就不会死。   log_write 是保存调试信息到 log 文件。
将上面代码中的  sync_mutex_give(SYNC_MUTEX_LOG);\  移动到   log_write  的下一行,也不会死。。。

或者将调试宏改成下面的形式,也不会死。。。  (死机问题解决后,堆栈改小到 16K 也没问题)
80715A30-5E01-4aee-82B1-566695D6F094.png


不是太明白这里面的具体区别,按我的理解,互斥量主要是保证 printf 的原子性,防止输出的内容被穿插。
snprintf 已经是可重入的,不需要另外保证。  在生成了 dbg_buf 的内容,且 printf 输出之后, log_write 应该可以不用互斥量保护也是能行的,但实际上会死机。
然而后面的代码把 snprintf  和 log_write  完全排除在 互斥量保护之外,却也不会死机。。。 这里就很难理解了。。

不知道大佬有何见解  
回复

使用道具 举报

77

主题

691

回帖

922

积分

金牌会员

积分
922
发表于 2022-1-27 22:14:57 | 显示全部楼层
ihavedone 发表于 2022-1-25 18:04
做了一些实验,发现在递归函数里调用简单的 printf("123"),或者 sprintf(buf, "123"); 等并不会死机。
...

递归的时候会压栈,sprintf这类函数压栈内容比较多,看一下是不是递归次数太多了。单片机最好别这样玩。否则再大堆栈也不够玩,也不会稳定。
回复

使用道具 举报

2

主题

269

回帖

275

积分

高级会员

积分
275
发表于 2022-1-28 00:20:56 | 显示全部楼层
庄永 发表于 2022-1-27 22:14
递归的时候会压栈,sprintf这类函数压栈内容比较多,看一下是不是递归次数太多了。单片机最好别这样玩。 ...

栈溢出了
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2024-5-13 22:17 , Processed in 0.210050 second(s), 30 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表