【问题标题】:What to do if an uninitialized pointer was used to write memory?如果使用未初始化的指针写入内存怎么办?
【发布时间】:2022-01-20 21:11:18
【问题描述】:

当使用未初始化的指针在系统中写入内存(在 C++ 中)时,例如在运行以下代码时,程序员可以做些什么来检测和/或纠正所产生的问题:

int main()
{
    int *ptr1;
    *ptr1 = 100;
}

【问题讨论】:

  • 使用最新的编译器并打开所有警告(例如 g++/clang 上的 -Wall,MSVC 上可能还有 /W4)。这将对此类代码发出警告
  • 希望它会使你的程序崩溃,(严重失败,快速失败)。但是你可能运气不好,数据段中的一些“随机”内存将被覆盖。还有一些静态代码分析工具可以检测到这类问题。另一种选择是尝试编写较少依赖指针的代码,如果必须使用指针,则使用 std::make_unique/std::unique_ptr(或 shared_ptr)。
  • 也许你应该阅读这个:isocpp.github.io/CppCoreGuidelines 并搜索所有出现的指针(或“原始指针”)
  • 必须通过修改有缺陷的代码来修复编程错误,使其不再包含编程错误。这是唯一理智的方式。无论想象中的轮椅代码以某种方式在运行时捕获错误,然后使用某种全能来确定程序员实际想要编写的 wtf 来修复由此产生的情况……都是不可能的。任何尝试都只会是更复杂的程序,有更多机会包含更多致命错误,但肯定会缺乏所需的实际心理能力。
  • 您还可以在 g++ 上启用-Wextra,这会启用一些-Wall 没有的警告

标签: c++ pointers memory


【解决方案1】:

任何最近的、体面的编译器都会对带有正确标志的此类代码发出警告。这里的一般建议是始终启用所有警告。 在 g++/clang 上使用 -Wall,在 MSVC (Visual C++) 上使用 /W4。

例如:

uninit.cpp:

int main()
{
    int *ptr1;
    *ptr1 = 100;
}

g++:

$ g++ -Wall uninit.cpp
uninit.cpp: In function ‘int main()’:
uninit.cpp:4:11: warning: ‘ptr1’ is used uninitialized in this function [-Wuninitialized]
    4 |     *ptr1 = 100;
      |     ~~~~~~^~~~~

叮当++:

$ clang++ -Wall uninit.cpp
uninit.cpp:4:6: warning: variable 'ptr1' is uninitialized when used here [-Wuninitialized]
    *ptr1 = 100;
     ^~~~
uninit.cpp:3:14: note: initialize the variable 'ptr1' to silence this warning
    int *ptr1;
             ^
              = nullptr
1 warning generated.

如您所见,编译器甚至会提示您需要使用哪些标志来专门打开这个单一警告 (-Wuninitialized)。

即使打开了警告,也会生成一个可执行文件。如果您希望编译在遇到警告时失败,请添加-Werror:

g++ -Wall -Werror uninit.cpp
uninit.cpp: In function ‘int main()’:
uninit.cpp:4:11: error: ‘ptr1’ is used uninitialized in this function [-Werror=uninitialized]
    4 |     *ptr1 = 100;
      |     ~~~~~~^~~~~
cc1plus: all warnings being treated as errors

您再次看到编译器为您指出必须添加什么标志才能仅将这一特定警告转变为错误 (-Werror=uninitialized)。

为了更通用地处理潜在的无效内存访问,您可以使用地址清理器:

g++ -fsanitize=address,undefined -g uninit.cpp

运行程序时的输出:

$ ./a.out
uninit.cpp:4:11: runtime error: store to null pointer of type 'int'
AddressSanitizer:DEADLYSIGNAL
=================================================================
==191==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x562eab7b9255 bp 0x7ffc74f9a750 sp 0x7ffc74f9a740 T0)
==191==The signal is caused by a WRITE memory access.
==191==Hint: address points to the zero page.
    #0 0x562eab7b9254 in main /home/someuser/uninit.cpp:4
    #1 0x7ff1e1d6e0b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
    #2 0x562eab7b912d in _start (/home/someuser/a.out+0x112d)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /home/someuser/uninit.cpp:4 in main
==191==ABORTING

这建立在额外的检查中,因此会带来性能成本;但是当出现更复杂的问题导致的访问问题时,它会在运行时警告您/停止程序

至于纠正此类错误,这取决于正确解决方案的上下文 - 没有适合每个用例的单一答案,除了通用 确保在使用指针之前对其进行初始化第一次。一个好的规则是总是在声明指针后直接初始化它们。这当然只会处理如上所示的简单问题,而不是指针有条件地设置为某个无效值的问题。使用一般的错误修复技术来解决这些问题;)

一般建议通常还包括尽可能避免使用原始指针。

【讨论】:

  • 警告不会正确停止编译...所以如果我们运行编译后的应用程序,我们应该怎么做才能检测到这样的写入错误(如果没有检查初始化)?
  • 你可以添加-Werror,这样会导致编译失败
  • 如果您正在寻找一些神奇的工具,可以在已编译的程序中自动检测并修复此类问题,祝您好运 - 我不知道这样的工具 ;)
【解决方案2】:

clang 和 GCC 都支持清理程序来捕获此类错误。例如:

int main()
{
    volatile int* p; // volatile so it's not removed at compile time

    *p = 0;
}

编译并运行它:

$ g++ main.cpp -O3 -fsanitize=undefined
$ ./a.out
main.cpp:5:8: runtime error: store to null pointer of type 'volatile int'

如您所见,有一条带有确切代码位置的明确错误消息。

【讨论】:

    【解决方案3】:

    当使用未初始化的指针时,程序员可以做些什么来纠正所产生的问题

    程序员可以更改程序,使程序中不使用不确定的值。您可以像这样修复示例程序:

    int main()
    {
    }
    

    当使用未初始化的指针时,程序员可以做些什么来检测...

    • 程序员可以读取源代码,并在使用不确定值时进行推断。

    • 如果程序员幸运的话,程序员可以阅读他们的程序在编译时可能产生的警告。如果是示例程序:

      warning: variable 'ptr1' is uninitialized when used here [-Wuninitialized]
      
    • 如果程序员幸运的话,程序员可以使用未定义的行为清理器,它们可能会在运行时检测到错误。如果是示例程序:

         /app/example.cpp:4:5: runtime error: store to address 0x7ffdda141688 with insufficient space for an object of type 'int'
        0x7ffdda141688: note: pointer points here
         00 00 00 00  f8 1e 14 da fd 7f 00 00  00 00 00 00 00 00 00 00  03 1f 14 da fd 7f 00 00  7a 1f 14 da
                    ^ 
        SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /app/example.cpp:4:5 in 
        AddressSanitizer:DEADLYSIGNAL
        =================================================================
        ==1==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x000000000000 bp 0x000000000000 sp 0x7ffdda141598 T0)
        ==1==Hint: pc points to the zero page.
        ==1==The signal is caused by a READ memory access.
        ==1==Hint: address points to the zero page.
            #0 0x0  (<unknown module>)
      
        AddressSanitizer can not provide additional info.
        SUMMARY: AddressSanitizer: SEGV (<unknown module>) 
        ==1==ABORTING
      
    • 只要代码发生更改,程序员就可以自动运行测试。这不仅有助于确保行为按预期实施,而且还为消毒人员提供了发现错误的机会。

    【讨论】:

    • 或者如果 OP 真的需要他的值:int value = 100; ;)
    • 我添加了 *ptr = 100; 用于演示目的...
    • @CoderAlpha 我删除了*ptr = 100; 以修复演示的错误。
    • 在大型程序中,如果视觉检测和删除很少可能,那么您如何处理这种情况?
    • @CoderAlpha 为了避免产生错误,您在编写代码时会花费大量时间。当您确实发现错误时,您通过修复它们来处理它们。如果由于某种原因无法修复,则停止使用该代码。
    【解决方案4】:

    为什么你不想将指针初始化为空。很多代码在取消引用指针之前会检查 null。

    int main()
    {
        int *ptr1 = nullptr;
        if(ptr1 != nullptr)
            *ptr1 = 100;
    }
    

    【讨论】:

      猜你喜欢
      • 2021-05-11
      • 2018-10-21
      • 2011-10-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-25
      • 2021-01-26
      • 1970-01-01
      相关资源
      最近更新 更多