考虑以下代码:
#include <stdio.h>
void badidea(int**);
int main(void) {
int* p;
badidea(&p);
printf("%d\n", *p); /* undefined behavior happens here: p points to x from badidea, which is now out of scope */
return 0;
}
void badidea(int** p) {
int x = 5;
*p = &x;
}
目的似乎是要打印5
,但是由于在main
中取消了对超出范围的局部变量的指针的引用,它实际上会调用未定义的行为。如何在代码库中找到此问题的实例?到目前为止,这是我尝试过的:
gcc -Wall -Wextra -pedantic
clang -Weverything
clang -fsanitize=undefined
进行编译运行valgrind
下运行以上均未产生任何警告。
答案 0 :(得分:2)
首先使用GCC 7.2和不 -fsanitize=address
进行编译,然后在Valgrind下运行会产生以下结果:
==25751== Conditional jump or move depends on uninitialised value(s)
==25751== at 0x4E988DA: vfprintf (vfprintf.c:1642)
==25751== by 0x4EA0F25: printf (printf.c:33)
==25751== by 0x1086E5: main (in ./a.out)
其次是其他警告。
答案 1 :(得分:0)
我们的CheckPointer工具可以使用动态分析以及各种各样的其他内存访问错误来检测到这一点。该工具跟踪所有涉及显式或隐式指针的分配,访问和分配,并在此类访问非法或未定义的最早时间进行投诉。
将OP的示例代码保存为“ buggy.c”,并运行CheckPointer会产生以下输出(出于教学原因,删除了某些行):
C~GCC4 CheckPointer Version 1.2.1001
Copyright (C) 2011-2016 Semantic Designs, Inc; All Rights Reserved; SD Confidential Powered by DMS (R) Software Reengineering Toolkit
Parsing source file "E:/DMS/Domains/C/GCC4/Tools/CheckPointer/Example/Source/buggy.c" using encoding CP-1252 +CRLF $^J $^M $^e -1 +8 ...
Grouping top level declarations ...
Creating object meta data initializers ...
Normalizing syntax tree ...
Instrumenting syntax tree ...
Ungrouping top level declarations ...
Writing target file "E:/DMS/Domains/C/GCC4/Tools/CheckPointer/Example/Target/buggy.c" using encoding CP-1252 +CRLF $^J $^M $^e -1 +8 ...
*** Compiling sources with memory access checking code gcc.exe -I"e:\DMS\Domains\C\GCC4\Tools\CheckPointer" -I.\Target -obuggy.exe Target\buggy.c Target\check-pointer-data-initializers.c "e:\DMS\Domains\C\GCC4\Tools\CheckPointer\check-pointer.c" "e:\DMS\Domains\C\GCC4\Tools\CheckPointer\check-pointer-splay-tree.c" "e:\DMS\Domains\C\GCC4\Tools\CheckPointer\check-pointer-wrappers.c"
*** Executing instrumented application
*** Error: CWE-465 Pointer Issue (subcategory CWE-476, CWE-587, CWE-824, or CWE-825)
Dereference of dangling pointer.
in function: main, line: 8, file E:/DMS/Domains/C/GCC4/Tools/CheckPointer/Example/Source/buggy.c
使用Common Weakness Enumeration标准定义的代码报告特定类型的错误。
NIST为Java和C错误提供了一种“折磨”测试,称为Juliet。 在与C语言相关的14,195个Juliet测试用例中,CheckPointer检测到13257个预期的内存访问错误。未诊断908个测试用例,但其中包括包含与指针使用错误(CheckPointer不想检测)无关的未定义行为或实际执行未暴露的指针使用错误(例如,未初始化的变量中包含0)实际执行)。 [我们修改了一些示例,以确保此类变量的实际执行不包含0,然后CheckPointer给出了预期的错误消息。]
CheckPointer可与GCC和MSVisualStudio一起使用。
=====================================
@ n.m。在此主题中对各种答案发表了许多评论。他提出了一种挑战性问题,他证明了valgrind在以下代码中找不到错误,类似于OP,但嵌套更深:
#include <stdio.h>
void badidea(int**);
void worseidea(int**);
int main(void) {
int* p;
badidea(&p);
// printf("%d\n", *p); /* undefined behavior happens here: p points to x from badidea, which is now out of scope */
worseidea(&p);
return 0;
}
void worseidea(int **p) {
int x = 42;
printf("%d %d\n", **p, x); /* undefined behavior happens here: p points to x from badidea, which is now out of scope */
}
void badidea(int** p) {
int x = 5;
*p = &x;
}
这是Checkpointer运行,它确实在n.m的代码中诊断了指针问题:
C~GCC4 CheckPointer Version 1.2.1001
Copyright (C) 2011-2016 Semantic Designs, Inc; All Rights Reserved; SD Confidential
...
Parsing source file "C:/Users/idbaxter/AppData/Local/Temp/DMS/Domains/C/GCC4/Tools/CheckPointer/Example/Source/buggy.c" using encoding CP-1252 +CRLF $^J $^M $^e -1 +8 ...
...
Writing target file "C:/Users/idbaxter/AppData/Local/Temp/DMS/Domains/C/GCC4/Tools/CheckPointer/Example/Target/buggy.c" using encoding CP-1252 +CRLF $^J $^M $^e -1 +8 ...
*** Compiling sources with memory access checking code
gcc.exe -I"c:\DMS\Domains\C\GCC4\Tools\CheckPointer" -I.\Target -obuggy.exe Target\buggy.c Target\check-pointer-data-initializers.c "c:\DMS\Domains\C\GCC4\Tools\CheckPointer\check-
pointer.c" "c:\DMS\Domains\C\GCC4\Tools\CheckPointer\check-pointer-splay-tree.c" "c:\DMS\Domains\C\GCC4\Tools\CheckPointer\check-pointer-wrappers.c"
*** Executing instrumented application
*** Error: CWE-465 Pointer Issue (subcategory CWE-476, CWE-587, CWE-824, or CWE-825)
Dereference of dangling pointer.
in function: worseidea, line: 16, file C:/Users/idbaxter/AppData/Local/Temp/DMS/Domains/C/GCC4/Tools/CheckPointer/Example/Source/buggy.c
called in function: main, line: 10, file: C:/Users/idbaxter/AppData/Local/Temp/DMS/Domains/C/GCC4/Tools/CheckPointer/Example/Source/buggy.c
答案 2 :(得分:-2)
我认为C语言中不存在这种机制,最后,指针仅仅是保存地址的变量。当给它们一个类型时,它只是告诉编译器指针所指向的地址空间中存在哪种变量。因此,理论上讲,指针可以保存任何地址值,只要它在定义的地址空间中即可。
实际上,这就是使C语言真正强大的原因。特别是在数据传输机制中,因为您可以按任意顺序发送任何类型的数据,甚至包括用户定义的结构等,并且可以轻松地在另一端进行接收/类型转换,而无需担心尾数等。
尽管在您的情况下,希望您知道程序的堆栈大小和起始地址,但是可以检查指针所指向的地址内容是否在为堆栈保留的区域中。这样就知道您是否指向局部变量。
+如果必须指向局部变量,则可以将其定义为静态变量,这会将变量置于RAM中堆栈的外部。 (您可能知道,但您可能不知道。)