【问题标题】:How to debug anomalous C memory/stack problems如何调试异常的 C 内存/堆栈问题
【发布时间】:2010-05-17 03:21:03
【问题描述】:

抱歉,我无法具体说明代码,但我看到的问题是异常的。字符串值似乎会根据其他不相关的代码而改变。例如,下面传递的参数的值将仅根据我是否注释掉一两个 fprintf() 调用而改变!到最后一个 fprintf() 值通常是完全空的(不,我已经检查以确保我没有直接修改参数......我所要做的就是注释掉 fprintf() 或添加另一个 fprintf()并且字符串的值会在某些点发生变化!):

static process_args(char *arg) {
    /* debug */
    fprintf(stderr, "Function arg is %s\n", arg);

    ...do a bunch of stuff including call another function that uses alloc()...

    /* debug */
    fprintf(stderr, "Function arg is now %s\n", arg);    
}

int main(int argc, char *argv[]) {
    char *my_arg;

    ... do a bunch of stuff ...

    /* just to show you it's nothing to do with the argv array */
    my_string = strdup(argv[1]);

    /* debug */
    fprintf(stderr, "Argument 1 is %s\n", my_string);

    process_args(my_string);
}

到处都有更多的代码,所以我不能要求别人调试我的程序——我想知道的是如何调试为什么像这样的字符串会根据不相关的代码改变或覆盖它们的内存。我的记忆力有限吗?我的堆栈太小了?我该怎么说?我还能做些什么来追踪问题?我的程序并不庞大,它就像一千行代码给或取和几个动态链接的外部库,但没有什么不寻常的。

帮助!蒂亚!

【问题讨论】:

    标签: c memory stack alloc


    【解决方案1】:

    简单:

    1. 学习使用Valgrind,特别是memcheck
    2. 学习使用GDB,包括断点和变量检查
    3. 熟能生巧。

    这应该对它进行排序。确保您使用 GCC 上的 -g 选项编译任何库,该选项会保留调试符号,因此您的调试输出更有意义。

    【讨论】:

    • 哇!快速回答!第一次 valgrind 尝试告诉我“权限被拒绝”来运行我的程序,所以我必须弄清楚。但是,如果我遇到内存问题,我要确保我的程序有足够的内存,第一步是什么?
    • 除非你的内存非常小,否则没问题。运行 memcheck 并检查输出。您将拥有比您想象的更多的东西,他们需要时间来了解他们的意思......他们通常看起来很神秘
    • 好吧,这就是我所希望的,但我很难过。 Valgrind 告诉我我泄露了 90 个字节,这看起来并不多,没有其他大错误,但如果是正确的 90 个字节,也许这就是问题所在。无论如何,我想我必须做一些挖掘和学习。谢谢。
    • 再次感谢。这似乎是一个范围问题。 valgrind 通过告诉我“有条件的跳转或移动取决于未初始化的值”来提醒我,这就是票。该字符串在函数中定义为字符数组(ala char [100]),我相信它超出了范围。将其声明更改为使用 malloc() 就可以了。现在奖励是 valgrind 已验证我没有错误。 :-)
    • 那总是甜蜜的。 “未发现泄漏”。祝一切顺利!
    【解决方案2】:

    有两种情况需要考虑:

    1. arg 变量在process_args 的开始和结束之间更改值。
    2. arg 保持不变,但它指向的字符串已更改。

    您的描述和您的代码不会区分这两者,但重要的是要知道两者中的哪一个实际发生。

    这将揭示答案:

    fprintf(stderr, "Function arg is %s (%p)\n", arg, (void *)arg);
    ... do bunch of stuff ...
    fprintf(stderr, "Function arg is now %s (%p)\n", arg, (void *)arg);
    

    大多数情况下arg 没有改变(即您有案例 2)。如果是这样,那么某些东西正在破坏您分配的字符串。 Valgrind,已经被建议,但仅在 Linux、AIX 和 MacOSX 上可用,只有 50:50 的机会找到问题。您真正想要的是一个 GDB 观察点:在 process_args 的开头设置断点,一旦命中,执行 (gdb) watch *(long*)argcontinue。当有东西写入*arg 时,GDB 将停止(实际上它会在下一条指令时停止)。然后使用(gdb) where 命令找出发生了什么。

    如果您确实让arg 更改了它的值(案例 1),这可能更难调试,并且表明某种堆栈损坏,或者违反了您平台的过程调用约定。 Valgrind 可能对此毫无帮助。这与您描述的行为更一致:注释掉不相关的代码会导致错误转移。

    不过,我无法就调试案例 1 提供任何进一步的建议,因为您还没有透露您的实际平台是什么。

    【讨论】:

    • 啊,查看指针地址,好建议。我这样做并验证了指针仍然是静态的。那么,数据/内存实际上就是正在发生变化的东西。我的平台是 FC5 我认为它是(是的,旧的,但它是离线的,仅测试)。嗯,你认为 valgrind 不会有帮助吗?好吧,我想我必须继续努力。感谢您的出色建议!
    • 是的 - GDB 观察点可能是这项工作的最佳工具(Valgrind 只能在你写入不应该写入的内存时提供帮助 - 而不是写入你应该写入的内存一个你不应该的地方)。要查找“案例 1”问题,您只需在 arg 本身 (watch arg) 上设置一个监视。
    • “watch arg”是没有用的,因为 arg 永远不会改变。你真正想要的是“watch *arg”,除了它只会检测到字符串第一个字符的变化。
    • 受雇于俄罗斯人,谢谢。我希望我可以选择两个答案,因为您的建议很棒并且非常感谢。问题是最初填充字符串的函数将其声明为字符数组,如下所示: char string[100];我认为这超出了范围。更改为此修复它: char *string = malloc(100);再次感谢。
    【解决方案3】:

    如果您正在编写用户级应用程序,Valgrind 是检测内存泄漏、缓冲区溢出等内存问题的不错选择。这里有一个快速入门指南:http://valgrind.org/docs/manual/QuickStart.html

    【讨论】:

    • 谢谢,这似乎是我接下来要去的地方
    【解决方案4】:

    您没有提供开发工具,但如果您像 90% 的编码人员一样,您已经在使用带有可以处理此问题的调试器的 IDE,无需新工具。对保存字符串的内存块设置监视,然后单步执行代码并查看字符串何时更改。

    【讨论】:

    • 不,没有 IDE。看起来 valgrind 是首选工具,所以我必须做一些学习。
    猜你喜欢
    • 1970-01-01
    • 2017-06-07
    • 2013-03-31
    • 2019-10-16
    • 2017-08-06
    • 1970-01-01
    • 2014-06-04
    • 2021-12-09
    相关资源
    最近更新 更多