notpad++设置黑暗_C ++的黑暗角落和陷阱

notpad++设置黑暗

介绍 (Introduction)

C++ is a very powerful and versatile tool, but you have to pay for this.

C ++是一个非常强大且用途广泛的工具,但是您必须为此付出代价。

As Bjarne Stroustrup once said:

正如Bjarne Stroustrup所说:

"C makes it easy to shoot yourself in the foot; C++ makes it harder, but when you do it blows your whole leg off".

C使脚上的射击变得容易; C ++使它更难,但是当您这样做时,它会使您的整个腿大吃一惊 ”。

That article would teach you how to completely shoot off all your legs (arms, heads and other parts) with the most interesting, unpredictable and exciting ways you can imagine!

那篇文章将教您如何用您可以想象的最有趣,不可预测和令人兴奋的方式完全拍打您的所有腿(手臂,头部和其他部位)!

背景 (Background)

In this article, we want to show how it is important to understand the aspects of writing a stable, safe and reliable code and how really easy it is to unintentionally inject vulnerability in it. We hope that would be both interesting and useful to you.

在本文中,我们想展示理解稳定,安全和可靠的代码编写方面的重要性,以及无意中向其中注入漏洞的真实程度。 我们希望这对您来说既有趣又有用。

到代码! (To the Code!)

Here is a short snippet of some abstract C++ code. As you can see, that is a code from the Windows DLL (and that point is really important!). Assume that someone is expecting to use that code in some (secure, of course!) solution.

这是一些抽象的C ++代码的简短片段。 如您所见,这是Windows DLL中的代码(这一点非常重要!)。 假设有人期望在某些(当然是安全的!)解决方案中使用该代码。

Take time looking at it. Who knows what you can find here? And what in this code could possibly go wrong?

花点时间看一下。 谁知道您在这里可以找到什么? 这段代码中可能发生什么错误?

// Singleton
class Finalizer
{
    struct Data
    {
        int i = 0;
        char* c = nullptr;
        
        union U
        {
            long double d;
            
            int i[sizeof(d) / sizeof(int)];
            
            char c [sizeof(i)];
        } u = {};
        
        time_t time;
    };
    
    struct DataNew;
    DataNew* data2 = nullptr;
    
    typedef DataNew* (*SpawnDataNewFunc)();
    SpawnDataNewFunc spawnDataNewFunc = nullptr;
    
    typedef Data* (*Func)();
    Func func = nullptr;
    
    Finalizer()
    {
        func = GetProcAddress(OTHER_LIB, "func")
        
        auto data = func();
        
        auto str = data->c;
        
        memset(str, 0, sizeof(str));
        
        data->u.d = 123456.789;
        
        const int i0 = data->u.i[sizeof(long double) - 1U];
        
        spawnDataNewFunc = GetProcAddress(OTHER_LIB, "SpawnDataNewFunc")
        data2 = spawnDataNewFunc();
    }
    
    ~Finalizer()
    {
        auto data = func();
        
        delete[] data2;
    }
};

Finalizer FINALIZER;

HMODULE OTHER_LIB;
std::vector<int>* INTEGERS;

DWORD WINAPI Init(LPVOID lpParam)
{
    OleInitialize(nullptr);
    
    ExitThread(0U);
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    static std::vector<std::thread::id> THREADS;
    
    switch (fdwReason)
    {
        case DLL_PROCESS_ATTACH:
            CoInitializeEx(nullptr, COINIT_MULTITHREADED);
            
            srand(time(nullptr));
            
            OTHER_LIB = LoadLibrary("B.dll");
            
            if (OTHER_LIB = nullptr)
                return FALSE;
            
            CreateThread(nullptr, 0U, &Init, nullptr, 0U, nullptr);
        break;
        
        case DLL_PROCESS_DETACH:
            CoUninitialize();
            
            OleUninitialize();
            {
                free(INTEGERS);
                
                const BOOL result = FreeLibrary(OTHER_LIB);
                
                if (!result)
                    throw new std::runtime_error("Required module was not loaded");
                
                return result;
            }
        break;
        
        case DLL_THREAD_ATTACH:
            THREADS.push_back(std::this_thread::get_id());
        break;
        
        case DLL_THREAD_DETACH:
            THREADS.pop_back();
        break;
    }
    return TRUE;
}

__declspec(dllexport) int Initialize(std::vector<int> integers, int& c) throw()
{
    for (int i : integers)
        i *= c;
    
    INTEGERS = new std::vector<int>(integers);
}

int Random()
{
    return rand() + rand();
}

__declspec(dllexport) long long int __cdecl _GetInt(int a)
{
    return 100 / a <= 0 ? a : a + 1 + Random();
}

Do you find this code quite simple, obvious, absolutely safe and hassle-free? Or maybe you found some problems here? Or maybe you even found a dozen or two?

您是否发现此代码非常简单,明显,绝对安全且无忧? 也许您在这里发现了一些问题? 也许您甚至发现了一打还是两个?

Well, actually there are more than 43 (yep, forty-three!) potential threats of varying degrees of significance in this code chunk.

嗯,实际上,此代码块中有超过43种 (是,四十三!)种不同程度的潜在威胁。

兴趣点 (Points of Interest)

  1. The sizeof(d) (where d is a long double) is not necessarily multiple of the sizeof(int)

    sizeof(d) (其中dlong double sizeof(int) long double )不一定是sizeof(int)倍数

    int i[sizeof(d) / sizeof(int)];
    
    	

    Such a situation is not checked nor handled here. For example, the size of a long double could be 10 on some platforms (which is not true for MS VS compiler, but true for a RAD studio, former C++ Builder).

    在此不检查也不处理这种情况。 例如,在某些平台上, long double的大小可能为10(对于MS VS编译器而言并非如此,但对于RAD studio (以前的C ++ Builder )而言则为true)。

    int can also be of different sizes depending on the platform (well, the code above is for Windows, so, applied specifically to that current situation, the problem is somewhat contrived, but for the portable code, the problem arises).

    根据平台的不同, int的大小也可以不同(嗯,以上代码是针对Windows的 ,因此,专门针对该当前情况,该问题在某种程度上是人为造成的,但对于可移植代码,会出现此问题)。

    https://www.viva64.com/en/t/0012

    All that would become a problem if we want a type punning here. By the way, a type punning causes an undefined behaviour due to the C++ language standard (yet still, that is a common practice, because modern compilers usually do define a correct, expected behaviour for that, like a GCC, for example).

    如果我们想要在这里修剪类型 ,那么所有这些都会成为问题。 顺便说一句,由于C ++语言标准, 类型调整会导致未定义的行为 (但仍然是一种惯例 ,因为现代编译器通常为此定义正确的预期行为例如GCC )。

    By the way, in modern C the type punning is perfectly allowed (you do understand that C and C++ are different languages and that you should not expect to know C if you know C++ and vice verse, do you?)

    顺便说一句, 在现代的C类型双关是完全允许的 (你也知道, CC ++是不同的语言和你应该指望了解C,如果你知道C ++,反之亦然,你呢?)

    The solution: Use static_assert to control all those kind of assumptions at the compile time. That would warn you if something with the type's sizes goes wrong:

    解决方案 :在编译时使用static_assert来控制所有这些假设。 如果某个类型的大小出现问题,这将警告您:

    static_assert(0U == (sizeof(d) % sizeof(int)),
                  "Size of the bigger type is not multiple of sizes of the smaller type");
    	
  2. time_t is a macro, in Visual Studio, it can refer to 32 (old) or 64 bit (new) integer type:

    time_t是一个宏,在Visual Studio中 ,它可以引用32(旧)或64位(新)整数类型:

    time_t time;
    
    	

    Accessing that can cause out of border reads/writes or type slicing (corrupting the memory or resulting in reading garbage) if two different binary modules (for example, an executable and a DLL, which it loads) are compiled with the different physical representation of that type.

    如果使用不同的物理表示形式编译了两个不同的二进制模块(例如,其加载的可执行文件和DLL) ,则可能导致超出边界的读/写或类型切片(导致内存损坏或读取垃圾)的访问。这种类型。

    The solution: Ensure the same strictly sized types are used to share the data between all communicating modules:

    解决方案 :确保使用相同大小的严格类型在所有通信模块之间共享数据:

    int64_t time;
    	
  3. B.dll (which should be referred by the OTHER_LIB handle) is not yet loaded at this point, so we will fail to attempt to get an address from it.

    B.DLL(应该由被称为OTHER_LIB手柄)还没有加载在这一点上,所以我们将无法尝试从它那里得到一个地址。

  4. static initialization order fiasco (OTHER_LIB object is used, while it is not yet initialized and contains garbage).

    静态初始化顺序的悲剧 ( OTHER_LIB对象使用,而它尚未初始化,包含垃圾)。

    func = GetProcAddress(OTHER_LIB, "func");
    
    	

    FINALIZER is a static object, which is constructed before a call to the DllMain. So in its constructor, we are attempting to use the library, which is loaded later. And the problem worsens because OTHER_LIB static object which is used by the FINALIZER static object is defined later than it in the translation unit, which means it would be initialized (zeroed) later. That means it will simply contain some pseudo-random garbage. Gladly WinAPI should handle that correctly, because with the high probability there will be no module loaded with such handle value, and even if it does exist - it would probably lack the "func" function in it (but if it eventually does, oh boy...)

    FINALIZER是一个静态对象,它在调用DllMain之前构造。 因此,在其构造函数中,我们尝试使用稍后加载的库。 和这个问题恶化,因为OTHER_LIB其用于由静态对象FINALIZER静态对象是在翻译单元,这意味着它会在后面初始化(清零)后比它定义。 这意味着它将仅包含一些伪随机垃圾。 很高兴WinAPI应该正确地处理该问题,因为很有可能不会有装载有该处理值的模块,即使它确实存在,它也可能缺少 func 功能(但是如果最终成功,哦,男孩...)

    The solution: The general hint is to avoid using global objects at all, especially complicated ones, especially if they are depending on each other, especially in the DLL. However, if you still need them for some reason, be very careful with their initialization order. To control that order, place all global objects instances (definitions) in the one translation unit in the correct order, to ensure they are initialized properly.

    解决方案 :一般提示是完全避免使用全局对象,尤其是复杂的对象,尤其是在它们相互依赖的情况下,尤其是在DLL中 。 但是,如果由于某些原因仍需要它们,请非常小心其初始化顺序。 要控制该顺序 ,请以正确的顺序将所有全局对象实例(定义)放在一个 转换单元中,以确保正确初始化它们。

  5. The previously returned result is not checked before use:

    使用之前,不会检查先前返回的结果:

    auto data = func();
    
    	

    func is a pointer to the function. It should point to the function from the B.dll. But, because we completely failed all the things in the previous step, it will be nullptr. So attempting to dereference it will lead to something interesting and fascinating like access violation or general protection fault, etc.

    func该函数指针 。 它应该指向B.dll中的函数。 但是,由于我们完全失败了上一步中的所有操作,因此它将为nullptr 。 因此尝试取消引用将导致一些有趣而令人着迷的事情,例如访问冲突或常规保护错误等。

    The solution: When dealing with the external code (WinAPI in our case), always check the return result of the provided functions. For reliable and fail-safe systems, this rule is still useful even if there are strict contracts that exist for those functions.

    解决方案 :处理外部代码(在本例中为WinAPI )时,请始终检查所提供函数的返回结果。 对于可靠且故障安全的系统,即使存在针对这些功能的严格合同,该规则仍然有用。

  6. Garbage if compiling with the different alignment/padding settings:

    如果使用不同的对齐方式/填充设置进行垃圾处理:

    auto str = data->c;
    
    	

    If Data struct (which is used to share information between the communicating modules) has different physical representation through the binary modules, we will end up in the previously mentioned access violation, general protection fault, segmentation-fault, heap corruption, etc. Or we will read garbage. An exact outcome depends on the actual scenario of using that memory. All of that could happen because the struct itself lacks an explicit alignment/padding settings, so in case those global settings were different for those communicating modules when they were compiled, we run into trouble.

    如果Data struct (用于在通信模块之间共享信息)通过二进制模块具有不同的物理表示形式,我们将最终遇到前面提到的访问冲突, 常规保护错误分段错误堆损坏等。或者会读垃圾。 确切的结果取决于使用该内存的实际情况。 由于struct本身缺少显式的对齐/填充设置,因此可能会发生所有这些情况,因此,如果在编译时这些通信模块的全局设置不同,那么我们就会遇到麻烦。

    The solution: Ensure all shared data structures have strict, explicitly defined and obvious physical representation (fixed-size types, alignment definition, etc.) and/or communicating binaries are compiled with the same alignment/padding settings.

    解决方案 :确保所有共享数据结构都具有严格的,明确定义的和明显的物理表示形式( 固定大小类型 ,对齐方式定义等)和/或使用相同的对齐方式/填充设置编译可通信的二进制文件。

    也可以看看 (See Also)
    也可以看看 (See Also)
  7. Using the size of the pointer instead of the size of an array, which it is pointed:

    使用指针的大小而不是所指向的数组的大小:

    memset(str, 0, sizeof(str));
    
    	

    That is usually a typo. But things can be complicated when dealing with static polymorphism or using auto keyword (especially when it is overused). I really hope modern compilers are already smart enough to detect such problems during the compilation phase using its internal static code analysis capabilities.

    通常是错字。 但是,在处理静态多态性或使用auto关键字时( 尤其是过度使用时 ),事情可能会变得复杂。 我真的希望现代编译器已经足够聪明,可以使用其内部静态代码分析功能在编译阶段检测到此类问题。

    解决方案 (The Solution)
    解决方案 (The Solution)
  8. UB when accessing another field, then one which was set

    UB访问另一个字段,然后设置一个

  9. Possible out of bound access if the size of long double differs between binary modules:

    如果二进制模块之间的long double大小不同,则可能超出范围:

    const int i0 = data->u.i[sizeof(long double) - 1U];
    
    	

    Well, that was already mentioned earlier, so here we just got another point of presence of previously discussed problems.

    好吧,这已经在前面提到过了,所以在这里我们只是有了前面讨论过的问题的另一个观点。

    The solution: Do not access another field, then one which was previously set, unless you are pretty sure your compiler handles that correctly. Ensure sizes of types of shared objects is the same in all communicating modules.

    解决办法不要访问其他字段,然后一个以前设定,除非你很肯定你的编译器句柄正确。 确保所有通信模块中共享对象类型的大小相同。

    See Also

    也可以看看

  10. Even if the B.dll was correctly loaded and "func" function is correctly exported and located, B.dll is anyway unloaded at this point (because of the FreeLibrary call in a DllMain/DLL_PROCESS_DETACH callback section), so we will get a crash here:

    即使正确加载了B.dll并正确导出并定位了 func 函数, B.dll仍会在此时卸载(由于DllMain / DLL_PROCESS_DETACH回调部分中的FreeLibrary调用),所以我们将崩溃这里:

    auto data = func();
    
    	

    Possibly, calling member function using the destroyed polymorphic object or calling the function from an unloaded dynamic library will lead to the pure virtual function call.

    可能地,使用已破坏的多态对象调用成员函数或从已卸载的动态库中调用该函数将导致纯虚函数调用

    The solution: Implement correct finalization routine in the application, ensuring all dynamic libraries finish their work and unloaded in the proper order. Avoid using static objects with complicated logic in the DLL. Avoid performing actions after the DLL finally exits its entry point (and starting to destroy the static objects).

    解决方案 :在应用程序中实施正确的完成例程,确保所有动态库完成工作并按正确的顺序卸载。 避免在DLL中使用具有复杂逻辑的静态对象。 避免在DLL最终退出其入口点后执行操作(并开始破坏静态对象)。

    Understand the DLL life cycle:

    了解DLL的生命周期

    ... other module calls LoadLibrary ...

    ...其他模块调用LoadLibrary ...

    1. construction of the library static objects (should contain only very simple logic, called automatically)

      库静态对象的构造( 应该只包含非常简单的逻辑,即自动调用 )

    2. DllMain -> DLL_PROCESS_ATTACH callback event (should contain only very simple logic, called automatically)

      DllMain > DLL_PROCESS_ATTACH回调事件( 应仅包含非常简单的逻辑,即自动调用 )

      [!!] From now other threads of the application can start calling

      [!!]从现在开始,应用程序的其他线程可以开始调用

      DllMain -> DLL_THREAD_ATTACH/DLL_THREAD_DETACH in parallel (called automatically, see notes on p. 30)

      DllMain > DLL_THREAD_ATTACH / DLL_THREAD_DETACH并行( 自动调用 ,请参阅第30页的注释)

      Those sections can possibly contain some complicated logic (like per thread random seeding) but still beware

      这些部分可能包含一些复杂的逻辑(例如每个线程随机播种),但仍要当心

    3. Custom initialization routine (exported by the DLL developer) is called.

      调用自定义初始化例程(由DLL开发人员导出)。

      (contains all the heavy initialization work, should be manually called by one, who is loading your library)

      ( 包含所有繁重的初始化工作,应由正在加载您的库的人手动调用 )

      [your library can create its own threads now and later]

      [您的库现在和以后都可以创建自己的线程]

      [..] the library performs its main work

      [..]图书馆执行其主要工作

    4. Custom deinitialization routine (exported by the DLL developer) is called.

      调用自定义de初始化例程(由DLL开发人员导出)。

      (Contains all the heavy finalization work, should be manually called by one, who loaded your library.)

      ( 包含所有繁重的完成工作,应该由加载您的库的人手动调用。 )

      [After this point, avoid performing any actions in your library, all previously started library threads should be finished before returning from that function.]

      [此后,请避免在您的库中执行任何操作,所有先前启动的库线程应从该函数返回之前完成。

      ... other module calls FreeLibrary ...

      ...其他模块调用FreeLibrary ...

    5. DllMain -> DLL_PROCESS_DETACH (should contain only very simple logic, called automatically)

      DllMain > DLL_PROCESS_DETACH ( 应该只包含非常简单的逻辑,自动调用 )

    6. Destruction of the library static objects (should contain only very simple logic, called automatically)

      销毁库静态对象( 应该只包含非常简单的逻辑,即自动调用 )

  11. Deleting an opaque pointer (the compiler needs to know a complete type to call the destructor, so deleting an object through opaque pointer can result in a memory leak and other problems)

    删除 不透明指针 ( 编译器需要知道完整的类型才能调用析构函数,因此通过不透明指针删除对象可能会导致内存泄漏和其他问题)

  12. (assuming the destructor of a DataNew is virtual) even if the class is correctly exported and imported and we got full information about it, still calling its destructor at this point is a problem - it will likely result in a pure virtual function call (as DataNew type is imported from already unloaded B.dll). And even if it is not (virtual), still, we got a problem here.

    (假设DataNew的析构函数是virtual ),即使该类已正确导出和导入并且我们获得了有关该类的完整信息,此时仍要调用其析构函数是一个问题-可能会导致virtual函数调用 (如从已卸载的B.dll中导入DataNew类型。 即使不是( virtual ), 我们仍然在这里遇到问题

  13. If DataNew class is an abstract polymorphic type and its base class has a pure virtual destructor without a body, there will be a pure virtual function call anyway

    如果DataNew类是一个抽象的多态类型,并且其基类具有一个没有主体的纯virtual析构函数,那么无论如何都会有一个纯virtual函数调用

  14. UB if allocated using new and deleting using delete[]

    UB,如果使用new分配,而使用delete[]

    delete[] data2;
    
    	

    In general, you should always be cautious when freeing and deleting objects received from the external modules.

    通常, 在释放和删除从外部模块收到的对象时 ,您应始终保持谨慎

    Also, it is a good practice to nullify the pointers on the deleted objects.

    同样, 将已删除对象上的指针设为也是一种好习惯。

    The solution is to ensure that:

    解决方案是确保:

    • when an object is being deleted, its full type (which is pointed by the pointer we deleting by) is known

      删除对象时,其完整类型(由我们删除的指针指向)是已知的
    • all destructors have a body

      所有破坏者都有一个身体
    • the library from which any code is exported is not unloaded too early

      从中导出任何代码的库都不会太早卸载
    • correct form of new and delete are always used

      始终使用正确的形式newdelete

    • the pointer pointing to the deleted object(s) is nullified

      指向已删除对象的指针无效

    Additionally Note That

    另外请注意

    See Also

    也可以看看

  15. ExitThread is the preferred method of exiting a thread in C code. In the C++ code, the thread is exited before any destructors can be called or any other automatic cleanup can be performed, so you should return from your thread function:

    ExitThread是退出C代码中线程的首选方法。 在C ++代码中,在可以调用任何析构函数或可以执行任何其他自动清除之前退出线程,因此您应该从线程函数返回:

    ExitThread(0U);
    
    	

    The solution: Never use this function manually in the C++ code, but rather just exit normally (by return statement) from the thread function.

    解决方案 :切勿在C ++代码中手动使用此函数,而应从线程函数正常退出(通过return语句)。

  16. Calling functions that require DLLs other than Kernel32.dll may result in problems that are difficult to diagnose. Calling User, Shell, and COM functions can cause access violation errors because some functions load other system components:

    调用需要非Kernel32.dll的 DLL的函数可能会导致难以诊断的问题。 调用UserShellCOM函数可能会导致访问冲突错误,因为某些函数会加载其他系统组件:

    CoInitializeEx(nullptr, COINIT_MULTITHREADED);
    
    	

    The solution - In the DllMain entry point:

    解决方案 -在DllMain入口点:

    • avoid any complicated (de)initialization

      避免任何复杂的(取消)初始化
    • avoid calling functions from the other libraries (or at least be very careful)

      避免从其他库调用函数(或至少要非常小心)
  17. Incorrect initialization of the random seed in a multithreaded environment

    多线程环境中随机种子的错误初始化
  18. As time has 1 sec. resolution, any threads in the program that calls time within that period will have the same seed, which could lead to collisions (for example, generating the same pseudo-random temporary file names, same port numbers, etc.). One of the possible solutions is to shuffle (xor) seed's bits with some other pseudo-random values, like the address of any stack or better heap object, more precise time, etc.

    由于time为1秒。 解决方案中,程序中在该时间段内调用time任何线程都将具有相同的种子,这可能导致冲突(例如,生成相同的伪随机临时文件名,相同的端口号等)。 一种可能的解决方案是用其他一些伪随机值(例如,任何堆栈或更好的堆对象的地址,更精确的时间等 )对种子的位进行洗牌( xor )。

    srand(time(nullptr));
    
    	

    The solution: MS VS requires that seed should be initialized per thread. Also, using Unix time as a seed gives not enough randomness, prefer to use more advanced seed generation.

    解决方案MS VS要求种子应按线程初始化 。 同样, 使用Unix时间作为种子不会产生足够的随机性而是 希望使用更高级的种子生成

    See Also

    也可以看看

  19. Can cause a deadlock or a crash (or create dependency loops in the DLL load order):

    可能导致死锁或崩溃(或按DLL加载顺序创建依赖关系循环):

    OTHER_LIB = LoadLibrary("B.dll");
    
    	

    The solution: Do not use LoadLibrary in the DllMain entry point. Any complicated (de)initialization should be done in the specific exported functions like "Init" and "Deint". Your module provides those functions as a result of a contract established between importing and exporting modules. Both parties must strictly enforce the contract.

    解决办法不要使用LoadLibraryDllMain入口点 。 任何复杂的(取消)初始化都应在特定的导出函数(如 Init Deint ”)中进行 。 由于在导入和导出模块之间建立了合同,因此模块提供了这些功能。 双方必须严格执行合同。

  20. Misprint (condition is always false), incorrect program logic and possible resource leak (since OTHER_LIB is never unloaded if loaded successfully):

    打印false (条件始终为false ),错误的程序逻辑和可能的资源泄漏(因为成功加载OTHER_LIB永远不会卸载):

    if (OTHER_LIB = nullptr)
        return FALSE;
    
    	

    Copy assignment operator returns left type reference, i.e., if would check the value of OTHER_LIB (which will be nullptr) and nullptr will be interpreted as false.

    复制分配运算符返回左类型引用,即, if将检查OTHER_LIB的值(将为nullptr ),则nullptr将被解释为false

    The solution: Always use reversed form to avoid such misprints:

    解决方案 :始终使用反转格式,以避免出现此类打印错误:

    if/while (<constant> == <variable/expression>)
    	
  21. Better use _beginthread (especially if linked to the static C run-time library) or you can get memory leaks in a call to the ExitThread, DisableThreadLibraryCalls.

    最好使用_beginthread (尤其是如果链接到静态C运行时库),否则在调用ExitThread DisableThreadLibraryCalls可能会发生内存泄漏。

  22. DLL notifications are serialized, the entry-point function (DllMain) should not attempt to create or communicate with other threads or processes (deadlocks may occur):

    DLL通知系列化,入口点函数( DllMain ) 不应试图创建或与其他线程或进程(通信死锁可能发生):

    CreateThread(nullptr, 0U, &Init, nullptr, 0U, nullptr);
    	
  23. Calling COM functions during termination can cause access violation errors because the corresponding component may already have been unloaded or uninitialized:

    在终止期间调用COM函数可能会导致访问冲突错误,因为相应的组件可能已经被卸载或未初始化:

    CoUninitialize();
    	
  24. There is no way to control the order in which in-process servers are loaded or unloaded, so do not call OleInitialize or OleUninitialize from the DllMain function:

    有没有办法来控制该命令进程内服务器加载或卸载,所以调用OleInitializeOleUninitializeDllMain函数:

    OleUninitialize();
    
    	

    See Also

    也可以看看

    COM Clients and Servers

    COM客户端和服务器

    In-process, Out-of-process, and Remote Servers

    进程内,进程外和远程服务器

  25. Calling free on memory block allocated with the new

    调用new分配的内存块上的free

  26. If the process is terminating (the lpvReserved parameter is non-NULL), all threads in the process except the current thread either have exited already or have been explicitly terminated by a call to the ExitProcess function, which might leave some process resources such as heaps in an inconsistent state, so it is not safe for the DLL to clean up the resources. Instead, the DLL should allow the operating system to reclaim the memory:

    如果进程正在终止( lpvReserved参数为非NULL ),则该进程中除当前线程外的所有线程要么已经退出,要么已通过调用ExitProcess函数显式终止,这可能会留下一些进程资源,例如处于不一致状态,因此DLL清理资源是不安全的。 而是, DLL应该允许操作系统回收内存:

    free(INTEGERS);
    
    	

    The solution: Ensure an old C style of dealing with the dynamic memory is not mixed with the modern C++ style. Be very careful when managing the resources in a DllMain entry point.

    解决方案 :确保不会将处理动态内存的旧C样式与现代C ++样式混合在一起。 在DllMain入口点中管理资源时要非常小心。

  27. Can result in a DLL being used after the system has executed its termination code:

    可能导致系统执行完终止代码后使用DLL

    const BOOL result = FreeLibrary(OTHER_LIB);
    
    	

    The solution: Do not call FreeLibrary in the DllMain entry point.

    解决办法不要调用FreeLibraryDllMain入口点。

  28. Will crash current (possibly main) thread:

    将导致当前(可能是主)线程崩溃:

    throw new std::runtime_error("Required module was not loaded");
    
    	

    The solution: Prefer not to throw exceptions in the DllMain entry point. If the DLL could not be loaded correctly for any reason, it should return FALSE. Throwing exceptions during the DLL_PROCESS_DETACH is not only a bad design approach (and almost meaningless) but also could possibly lead to the problems during the deinitialization stage.

    解决方案 :最好不要DllMain入口点中引发异常。 如果由于某种原因无法正确加载DLL ,则应返回FALSE 。 在DLL_PROCESS_DETACH期间引发异常不仅是一种错误的设计方法(并且几乎没有意义),而且还可能导致在反初始化阶段出现问题。

    In any case, always be very careful throwing exceptions outside of the DLL. Any complicated objects (like classes of the standard library) may have a different physical representation (and even logic) in some cases, for example, if two binary modules are compiled with the different (incompatible) versions of the runtime library.

    无论如何,请务必非常小心地将异常抛出到DLL之外。 在某些情况下,例如,如果两个二进制模块使用运行时库的不同(不兼容)版本进行编译,则任何复杂的对象(例如标准库的类)在某些情况下都可能具有不同的物理表示(甚至是逻辑)。

    Prefer to exchange only simple data types (with fixed sizes and determined representation) between modules.

    最好只在模块之间交换简单的数据类型 (具有固定的大小和确定的表示形式)。

    Also, remember, that exiting or terminating the main thread will automatically terminate all the others (which would not have a chance to be finished correctly, so they can corrupt the memory, leaving mutexes, heaps and other objects in the unpredictable, inconsistent state, and also those threads would be already dead at the time when the static objects will start their own deconstruction, so do not attempt to wait for a thread here).

    另外,请记住,退出或终止主线程会自动终止所有其他线程(它们没有机会正确完成,因此它们可能损坏内存,使互斥体,堆和其他对象处于不可预测的不一致状态,并且这些线程在静态对象将开始其自身的解构时将已经死掉,因此请勿尝试在此处等待线程)。

    See Also

    也可以看看

  29. Can throw an exception (std::bad_alloc, for example), which is not caught here:

    可以引发异常 (例如std::bad_alloc ),该异常在此处未捕获:

    THREADS.push_back(std::this_thread::get_id());
    
    	

    Since DLL_THREAD_ATTACH section is invoked from some unknown external code, do not expect the correct behaviour here.

    由于DLL_THREAD_ATTACH节是从某些未知的外部代码中调用的,因此不要期望此处的行为正确。

    The solution: Enclose with the try/catch those instructions, which could possibly throw exceptions that can't be expected to be handled correctly (especially if they go out of the DLL).

    解决方案try / catch这些指令,可能会引发无法预期正确处理的异常(尤其是如果它们超出了DLL )。

    See Also

    也可以看看

  30. UB if there were threads presented before this DLL was loaded:

    UB,如果在加载此DLL 之前出现了线程:

    THREADS.pop_back();
    
    	

    Existing threads (including that one which is actually loading the DLL) do not call the entry-point function of the newly loaded DLL (so they are not registered in the THREADS vector during DLL_THREAD_ATTACH event), while they still call it with DLL_THREAD_DETACH on finishing.

    现有线程(包括实际正在加载DLL的线程)不会调用新加载的DLL的入口点功能(因此它们在DLL_THREAD_ATTACH事件期间未在THREADS向量中注册),尽管它们在完成时仍使用DLL_THREAD_DETACH进行调用。

    Which means a consideration that a number of calls to the DLL_THREAD_ATTACH and DLL_THREAD_DETACH are always equal is wrong, those making any logic depending on it dangerous.

    这意味着对DLL_THREAD_ATTACHDLL_THREAD_DETACH的调用次数总是相等的考虑是错误的 ,那些使任何逻辑依赖DLL_THREAD_ATTACH的调用都是危险的。

  31. Compiler dependent int size, better use C++11 fixed-size integer

    取决于编译器的int大小,最好使用C ++ 11固定大小的整数

  32. Passing complicated object between modules (can cause a crash if the are compiled with the different runtimes: release/debug, different versions, etc.)

    在模块之间传递复杂的对象(如果使用不同的运行时(如发布/调试,不同的版本等)编译,可能会导致崩溃。
  33. Accessing object c by its virtual address (which is shared between modules) can cause problems, if pointers are threated differently in those modules (for example, if the modules are linked with the different [/LARGEADDRESSAWARE] options)

    如果指针c的虚拟地址(在模块之间共享)访问对象c会导致问题,如果这些模块中的指针受到不同的威胁(例如,如果模块使用不同的[/ LARGEADDRESSAWARE ]选项链接)

    __declspec(dllexport) int Initialize(std::vector<int> integers, int& c) throw()
    
    	

    See Also

    也可以看看

    And Also...

    还有 ...

    And Finally...

    最后 ...

    Wait, did I forgot something? Surely I did! :)

    等一下,我忘了什么吗? 当然可以! :)

    Because pointers are, in fact, much more complicated stuff than people usually think about them. I am pretty sure you can add something important in the comments (maybe something about the difference between pointer to object and a pointer to the function, that perhaps not all the bits in a pointer value can be used to form an address and so on).

    因为实际上指针是比人们通常认为的要复杂得多的东西。 我很确定您可以在注释中添加一些重要的内容(也许是关于对象指针和函数指针之间区别的东西,也许不是指针值中的所有位都可以用来形成地址,依此类推) 。

    for (int i : integers)
    
        i *= c;
    
    	

    Mistake: Original items in the container would not change, need to use a reference (prefer to use two types of references: 1 and 2:)

    错误 :容器中的原始项目不会更改,需要使用引用(最好使用两种引用类型: 12 :)

  34. Exception can be thrown inside the function:

    例外 可以扔在函数内部:

    INTEGERS = new std::vector<int>(integers);
    
    	

    However, that function's throw specification is empty:

    但是,该函数的throw规范为空:

    __declspec(dllexport) int Initialize(std::vector<int> integers, int& c) throw()
    
    	

    std::unexpected is called by the C++ runtime when a dynamic exception specification is violated: an exception is thrown from a function whose exception specification forbids exceptions of this type.

    违反动态异常规范C ++运行时将调用std::unexpected :从某个函数抛出异常,该函数的异常规范禁止此类异常。

    The solution: Use try/catch (especially when allocating resources, especially in the DLL) or use nothrow form. In any case, do not expect infinite resources.

    解决方案 :使用try / catch (尤其是在分配资源时,尤其是在DLL中 )或使用nothrow形式 。 无论如何, 不要指望无限的资源

    See Also

    也可以看看

    Problem 1: Forming such a "more random" value is incorrect. As the сentral limit theorem states, a sum of the independent random variables tends toward a normal distribution (even if the original variables themselves are not normally distributed).

    问题1 :形成这样的“ 更随机 ”值是不正确的。 正如中心极限定理指出的那样,独立随机变量之和趋于正态分布 (即使原始变量本身不是正态分布)。

    Problem 2: Possible integer overflow (which is UB for signed integers):

    问题2 :可能的整数溢出( 对于有符号整数 ,它是UB ):

    return rand() + rand();
    
    	

    When dealing with such things like randomization, encryption, etc., beware of using some homemade "solutions". If you lacking a specific math education and knowledge, heavy experience with those concepts, chances are high that you will simply outsmart yourself, making things worse.

    在处理诸如随机化,加密等问题时,请注意不要使用一些自制的“ 解决方案 ”。 如果您缺乏特定的数学教育和知识,并且对这些概念缺乏丰富的经验,那么您很可能会简单地超越自己,使情况变得更糟。

  35. The exported function name will be decorated (mangled), to prevent this use extern "C"

    导出的函数名称将进行修饰(修饰) ,以防止使用此外部extern "C"

  36. Names started from '_' are implicitly forbidden for C++, as that naming style is reserved for STL

    对于C ++ ,从'_'开头的名称被隐式禁止,因为该命名风格是为STL保留的

    __declspec(dllexport) long long int __cdecl _GetInt(int a)
    
    	

    Multiple problems (and their possible solutions):

    多个问题 (及其可能的解决方案 ):

  37. rand is not thread-safe, need to use rand_r/rand_s instead

    rand 不是线程安全的,需要使用rand_r / rand_s代替

  38. rand is obsolete, consider to use modern C++11 <random>

    rand已过时,请考虑使用现代C ++ 11 <random>

  39. The seed for rand was not possibly initialized for this thread (MS VS requires per-thread initialization)

    rand的种子可能未为此线程初始化( MS VS需要按线程初始化)

  40. It is not crypt safe, use specific OS API or some portable solution (Libsodium/randombytes_buf, OpenSSL/RAND_bytes, etc.)

    它不是安全的加密 货币 ,请使用特定的OS API或某些便携式解决方案( Libsodium / randombytes_bufOpenSSL / RAND_bytes等)

  41. Possible division by zero: can cause current thread termination

    可能被零除 :可能导致当前线程终止

  42. Operators priority (use brackets) and/or sequence points

    操作员优先级 (使用方括号)和/或顺序点

  43. Possible integer overflow:

    可能的整数溢出

    return 100 / a <= 0 ? a : a + 1 + Random();
    
    	

    See Also

    也可以看看

    And Also...

    还有 ...

那不是全部! 我们为您提供了更多有趣的代码;) (That's Not All! We Have Even More Intriguing Code for You ;))

Imagine you have some important content in memory (user password, for example). Surely you don't want to keep it in memory for a long time (increasing the probability someone could read it from here).

假设您的内存中有一些重要内容(例如,用户密码)。 当然, 您不想将其长时间保存在内存中(增加了有人可以从此处读取它的可能性)。

A naive approach to achieve that would look like that:

一个简单的方法来实现这一目标看起来像这样:

bool login(char* const userNameBuf, const size_t userNameBufSize,
           char* const pwdBuf, const size_t pwdBufSize) throw()
{
    if (nullptr == userNameBuf || '\0' == *userNameBuf || nullptr == pwdBuf)
        return false;
    
    // Here is some actual implementation, which does not checks params
    //  nor does it care of the 'userNameBuf' or 'pwdBuf' lifetime,
    //   while both of them obviously contains private information 
    const bool result = doLoginInternall(userNameBuf, pwdBuf);
    
    // We want to minimize the time this private information is stored within the memory
    memset(userNameBuf, 0, userNameBufSize);
    memset(pwdBuf, 0, pwdBufSize);
}

Well, that, of course, would not work. So, what to do then?

嗯,这当然是行不通的。 那么,该怎么办呢?

Wrong "solution" #1: If memset isn't working, let's do that manually!

错误的“ 解决方案” #1 :如果memset不起作用,让我们手动进行操作!

void clearMemory(char* const memBuf, const size_t memBufSize) throw()
{
    if (!memBuf || memBufSize < 1U)
        return;
    
    for (size_t idx = 0U; idx < memBufSize; ++idx)
        memBuf[idx] = '\0';
}

And there is no reason why the modern compiler can't optimize that.

而且,现代编译器没有理由无法优化

Btw, the memset function would be compiler intrinsic if they are enabled. That changes nothing in the current context, just an interesting thing to know.

顺便说一句,如果启用了memset函数,则它们将是编译器固有的 。 在当前情况下,这没有改变,只是一件有趣的事情。

See Also

也可以看看

Wrong "solution" #2: Trying to "improve" the previous "solution" by playing with the volatile keyword:

错误的“ 解决方案 ”#2 :尝试通过使用volatile关键字来“ 改进 ”先前的“ 解决方案 ”:

void clearMemory(volatile char* const volatile memBuf, const volatile size_t memBufSize) throw()
{
    if (!memBuf || memBufSize < 1U)
        return;
    
    for (volatile size_t idx = 0U; idx < memBufSize; ++idx)
        memBuf[idx] = '\0';
    
    *(volatile char*)memBuf = *(volatile char*)memBuf;
    // There is also possibility for someone to remove this "useless" code in the future
}

Would that work? Well, it might. Probably. For example, such an approach is used in the MS VS RtlSecureZeroMemory (you can check its actual implementation in the Windows SDK sources). However, this is heavily compiler-dependent.

那行得通吗? 好吧,可能吧。 大概。 例如, MS VS RtlSecureZeroMemory使用了这种方法(您可以在Windows SDK源中检查其实际实现)。 但是, 这在很大程度上取决于编译器

See Also

也可以看看

Wrong "solution" #3: Try to use wrong OS API (like RtlZeroMemory) or even STL (like std::fill, std::for_each) instead of the CRT or homemade code:

错误的“ 解决方案 ”#3 :尝试使用错误的OS API (例如RtlZeroMemory )甚至STL (例如std::fillstd::for_each )代替CRT或自制代码:

RtlZeroMemory(memBuf, memBufSize);

And there are even more possibly wrong solutions!

甚至还有更多错误的解决方案!

最后,如何真正解决该问题? (And, Finally, How to Really Fix That?)

  1. Use a specific OS API function, like RtlSecureZeroMemory for Windows

    使用特定的OS API函数,例如Windows的 RtlSecureZeroMemory

  2. C11 function memset_s is also suitable for that purpose:

    C11函数memset_s也适用memset_s目的:

Quote:
引用:

Unlike memset, any call to the memset_s function shall be evaluated strictly according to the rules of the abstract machine.

与memset不同,对memset_s函数的任何调用均应严格根据抽象机的规则进行评估。

Also, we can prevent the compiler from optimizing the code out by outputting (to the file, console or another stream) the variable value, but this way obviously is not very useful.

另外,我们可以通过输出变量值(到文件,控制台或其他流)来防止编译器优化代码,但是这种方式显然不是很有用。

未完待续... (To Be Continued...)

That is, of course, is not a complete list of all the possible troubles you can encounter writing applications using C/C++.

当然,这并不是使用C / C ++编写应用程序时可能遇到的所有麻烦的完整列表。

There are also such wonderful things like livelocks, race conditions (for example, caused by incorrect implementations of a none-blocking algorithm, ABA problems, improperly changing multiple atomics at once, thread-unsafe reference counters, incorrect implementations of a double-checking lock pattern and so on), objects slicing, loss of arithmetic precision (due to rounding or numerically unstable algorithms, for example, summation of many doubles without first sorting them), threads and GDI objects, volatile vs atomic, incorrect using of an integer literals (603 vs 0603), time-of-check to time-of-use, lambdas which outlives their reference captured objects, incorrect printf-family functions formatters, incorrectly sharing data between two devices with the different endianness (for example, through the network), bitfield details, confusing C++ exceptions and SEH, performing incorrect stack allocations, disabling ASLR, possible backdoors in API, confusing sizeof vs _countof, not using correct memory locking (also not that suspend mode on laptops and some desktop computers will save a copy of the system's RAM to disk, some architecture surprises, regardless of memory locks), stack corruptions, etc. etc. etc.

还有一些很棒的事情,例如活锁竞争条件 (例如,由无阻塞算法的不正确实现, ABA问题 ,一次不正确地更改多个原子 ,线程不安全的引用计数器双重检查锁的不正确实现引起的)模式等), 对象切片算术精度损失 (由于舍入或数值不稳定的算法,例如,许多双精度值的加总而未先对其排序), 线程和GDI对象易失性与原子性 ,不正确使用整数文字(603 vs 0603),使用时间检查 ,lambda超出其参考捕获对象的寿命不正确的printf系列功能格式化程序 ,不正确地在两个字节序不同的设备之间共享数据(例如,通过网络) ), 位域详细信息 ,令人困惑的C ++异常和SEH ,执行错误的堆栈分配 ,禁用ASLRAPI中可能存在的后门程序 ,令人困惑的sizeof_coun tof使用正确的内存锁定 (也不是笔记本电脑和某些台式机的挂起模式会将系统RAM的副本保存到磁盘,某些体系结构意外 ,无论内存锁定如何),堆栈损坏等等等。

Want to add more? Share your own interesting materials in the comments!

要添加更多吗? 在评论中分享您自己的有趣材料!

翻译自: https://www.codeproject.com/Articles/5164537/Dark-Corners-and-Pitfalls-of-Cplusplus

notpad++设置黑暗

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值