Make your code more powerful (making your c ++ code robust)

Source: Internet
Author: User
Tags define local
Making your c ++ code robust
  • Introduction

In actual projects, when the amount of code in the project increases, you will find it increasingly difficult to manage and track its components. If it is not good, it is easy to introduce bugs. Therefore, we should master some methods that make our programs more robust.

This article provides some suggestions to guide us to write stronger code to avoid catastrophic errors. Even because of its complexity and the structure of the project team, your program does not currently comply with any encoding rules. The simple rules listed below can help you avoid most crashes.

  • Background

Let's first introduce the author's development of some software (crashrpt). You can download the source code at http://code.google.com/p/crashrpt/website. Crashrpt, as its name implies, is a software crash record software (database) that automatically submits software error records installed on your computer. It directly sends these error records to you over Ethernet, so that you can track software problems and modify them in time, so that users can feel that the software released each time has greatly improved, in this way, they are naturally very happy.

Figure 1. dialog box popped up when the crashrpt database detects an error

When analyzing the received error records, we found that using the methods described below can avoid most program crash errors. For example, if the local variable is not initialized, the array access is out of bounds, and the pointer is not detected before use (null), leading to access to illegal regions.

I have summarized several code design methods and rules, which are listed in the following sections, hoping to help you avoid mistakes and make your program more robust.

  • Initializing local variables

Using uninitialized local variables is a common cause of program crash. For example, let's look at the following program snippet:

  // Define local variables  BOOL bExitResult; // This will be TRUE if the function exits successfully  FILE* f; // Handle to file  TCHAR szBuffer[_MAX_PATH];   // String buffer      // Do something with variables above... 

The above code has a potential error because no local variable is initialized. When your code runs, these variables will be negative by default. For example, the bexitresult value will be negative-135913245, And the szbuffer must end with "\ 0", but the result will not. Therefore, local variables are very important during initialization. The following code is correct:

  // Define local variables    // Initialize function exit code with FALSE to indicate failure assumption  BOOL bExitResult = FALSE; // This will be TRUE if the function exits successfully  // Initialize file handle with NULL  FILE* f = NULL; // Handle to file  // Initialize string buffer with empty string  TCHAR szBuffer[_MAX_PATH] = _T("");   // String buffer  // Do something with variables above... 

Note: Some people say that variable initialization will lead to lower program efficiency. Yes, it is true. If you really care about program execution efficiency and remove local variable initialization, you have to think about the consequences.

  • Initializing winapi Structures

Many windows APIs accept or return some struct parameters. If the struct is not correctly initialized, the program may crash. You may remember to use the zeromemory macro or memset () function to fill the struct with 0 (set the default value for the elements corresponding to the struct ). However, most Windows API structures must have a cbsize parameter, which must be set to the size of the struct.

Let's take a look at the following code to initialize the Windows API struct parameters:

  NOTIFYICONDATA nf; // WinAPI structure  memset(&nf,0,sizeof(NOTIFYICONDATA)); // Zero memory  nf.cbSize = sizeof(NOTIFYICONDATA); // Set structure size!  // Initialize other structure members  nf.hWnd = hWndParent;  nf.uID = 0;     nf.uFlags = NIF_ICON | NIF_TIP;  nf.hIcon = ::LoadIcon(NULL, IDI_APPLICATION);  _tcscpy_s(nf.szTip, 128, _T("Popup Tip Text"));          // Add a tray icon  Shell_NotifyIcon(NIM_ADD, &nf);

Note: Do not use zeromemory and memset to initialize the struct including the struct object. This can easily destroy the internal struct and cause program crash.

// Declare a C ++ structure struct iteminfo {STD: String sitemname; // The structure has STD: String object inside int nitemvalue ;}; // init the structure iteminfo item; // do not use memset ()! It can reset upt the structure // memset (& item, 0, sizeof (iteminfo); // instead use the following item. sitemname = "Item1"; item. nitemvalue = 0; it is best to use the constructor of the struct to initialize its members. // declare a C ++ structure struct iteminfo {// use structure constructor to set members with default values iteminfo () {sitemname = _ T ("unknown "); nitemvalue =-1;} STD: String sitemname; // The structure has STD: String object Inside int nitemvalue;}; // init the structure iteminfo item; // do not use memset ()! It can reset upt the structure // memset (& item, 0, sizeof (iteminfo); // instead use the following item. sitemname = "Item1"; item. nitemvalue = 0;
  • Validating function input

During function design, it is always recommended to check input parameters. For example, if the function you designed is part of a public API, it may be called by an external client, so it is difficult to ensure that the parameters passed by the client are correct.

For example, let's take a look at this hypotethical drawvehicle () function, which can draw a sports car based on different quality. The ndrawingqaulity is 0 ~ 100. Prcdraw defines the outline area of the sports car.

Take a look at the following code and observe how we perform parameter detection before using function parameters:

BOOL DrawVehicle(HWND hWnd, LPRECT prcDraw, int nDrawingQuality)  {    // Check that window is valid    if(!IsWindow(hWnd))      return FALSE;     // Check that drawing rect is valid    if(prcDraw==NULL)      return FALSE;     // Check drawing quality is valid    if(nDrawingQuality<0 || nDrawingQuality>100)      return FALSE;       // Now it's safe to draw the vehicle     // ...     return TRUE;  }
  • Validating pointers

Before using pointers, non-detection is very common. This can be said to be the most likely cause of software crash. If you use a pointer and the pointer is null, an exception will be reported during running of your program.

  CVehicle* pVehicle = GetCurrentVehicle();    // Validate pointer  if(pVehicle==NULL)  {    // Invalid pointer, do not use it!    return FALSE;  }
  • Initializing function output

If your function creates an object and uses it as the return parameter of the function. So remember to copy it to null before use. Otherwise, the caller of this function will use this Invalid Pointer and cause a program error. The following error code:

Int createvehicle (cvehicle ** ppvehicle) {If (cancreatevehicle () {* ppvehicle = new cvehicle (); return 1 ;}// if cancreatevehicle () returns false, // The pointer to * ppvehcile wocould never be set! Return 0;} the correct code is as follows; int createvehicle (cvehicle ** ppvehicle) {// first initialize the output parameter with null * ppvehicle = NULL; If (cancreatevehicle ()) {* ppvehicle = new cvehicle (); return 1 ;}return 0 ;}
  • Cleaning up pointers to deleted objects

After the memory is released, the pointer is copied to null. In this way, we can ensure that invalid pointers are no longer used in the program. In fact, accessing a deleted object address will cause program exceptions. The following code clears an object pointed to by a pointer:

 // Create object CVehicle* pVehicle = new CVehicle(); delete pVehicle; // Free pointer pVehicle = NULL; // Set pointer with NULL
  • Cleaning up released handles

Before releasing a handle, copy the handle to a pseudo null value (0 or other default values ). This ensures that invalid handles are not reused elsewhere in the program. See the following code to clear the file handle of a Windows API:

  HANDLE hFile = INVALID_HANDLE_VALUE;     // Open file  hFile = CreateFile(_T("example.dat"), FILE_READ|FILE_WRITE, FILE_OPEN_EXISTING);  if(hFile==INVALID_HANDLE_VALUE)  {    return FALSE; // Error opening file  }   // Do something with file   // Finally, close the handle  if(hFile!=INVALID_HANDLE_VALUE)  {    CloseHandle(hFile);   // Close handle to file    hFile = INVALID_HANDLE_VALUE;   // Clean up handle  } 

The following code clears the file * handle:

  // First init file handle pointer with NULL  FILE* f = NULL;   // Open handle to file  errno_t err = _tfopen_s(_T("example.dat"), _T("rb"));  if(err!=0 || f==NULL)    return FALSE; // Error opening file   // Do something with file   // When finished, close the handle  if(f!=NULL) // Check that handle is valid  {    fclose(f);    f = NULL; // Clean up pointer to handle  } 
  • Using Delete [] Operator for Arrays

If you allocate a separate object, you can use new directly. When you release a single object, you can use Delete directly. however, you can use new when applying for an object array object, but you cannot use Delete when releasing it. Instead, you must use Delete []:

 // Create an array of objects CVehicle* paVehicles = new CVehicle[10]; delete [] paVehicles; // Free pointer to array paVehicles = NULL; // Set pointer with NULLor // Create a buffer of bytes LPBYTE pBuffer = new BYTE[255]; delete [] pBuffer; // Free pointer to array pBuffer = NULL; // Set pointer with NULL
  • Allocating memory carefully

Sometimes, the program needs to dynamically allocate a buffer, which is determined when the program is running. For example, if you want to read the content of a file, you need to apply for a buffer of the file size to save the content of the file. Before applying for the memory, note that malloc () or new cannot apply for a 0-byte memory. Otherwise, malloc () or new function calls will fail. Passing an incorrect parameter to the malloc () function will cause a C runtime error. The following code shows how to dynamically apply for memory:

  // Determine what buffer to allocate.  UINT uBufferSize = GetBufferSize();    LPBYTE* pBuffer = NULL; // Init pointer to buffer   // Allocate a buffer only if buffer size > 0  if(uBufferSize>0)   pBuffer = new BYTE[uBufferSize];

To learn more about how to properly allocate memory, read the secure coding best practices for memory allocation in C and C ++ article.

  • Using asserts carefully

Asserts language debugging mode detection prerequisites and post conditions. However, when our compiler is in the release mode, asserts is removed during the pre-encoding phase. Therefore, asserts cannot detect the program status. The error code is as follows:

 #include <assert.h>    // This function reads a sports car's model from a file  CVehicle* ReadVehicleModelFromFile(LPCTSTR szFileName)  {    CVehicle* pVehicle = NULL; // Pointer to vehicle object     // Check preconditions    assert(szFileName!=NULL); // This will be removed by preprocessor in Release mode!    assert(_tcslen(szFileName)!=0); // This will be removed in Release mode!     // Open the file    FILE* f = _tfopen(szFileName, _T("rt"));     // Create new CVehicle object    pVehicle = new CVehicle();     // Read vehicle model from file     // Check postcondition     assert(pVehicle->GetWheelCount()==4); // This will be removed in Release mode!     // Return pointer to the vehicle object    return pVehicle;  }

Looking at the above Code, asserts can detect our programs in debug mode, but not in release mode. Therefore, we still have to use if () for this step. The correct code is as follows;

 CVehicle* ReadVehicleModelFromFile(LPCTSTR szFileName, )  {    CVehicle* pVehicle = NULL; // Pointer to vehicle object     // Check preconditions    assert(szFileName!=NULL); // This will be removed by preprocessor in Release mode!    assert(_tcslen(szFileName)!=0); // This will be removed in Release mode!     if(szFileName==NULL || _tcslen(szFileName)==0)      return NULL; // Invalid input parameter     // Open the file    FILE* f = _tfopen(szFileName, _T("rt"));     // Create new CVehicle object    pVehicle = new CVehicle();     // Read vehicle model from file     // Check postcondition     assert(pVehicle->GetWheelCount()==4); // This will be removed in Release mode!     if(pVehicle->GetWheelCount()!=4)    {       // Oops... an invalid wheel count was encountered!        delete pVehicle;       pVehicle = NULL;    }     // Return pointer to the vehicle object    return pVehicle;  }
  • Checking return code of a function

It is a common mistake to determine whether a function is successfully executed. When you call a function, we recommend that you check the return code and return parameter values. The following code continuously calls the Windows API. Whether the program continues to execute depends on the returned results and returned parameter values of the function.

HRESULT hres = E_FAIL;    IWbemServices *pSvc = NULL;    IWbemLocator *pLoc = NULL;        hres =  CoInitializeSecurity(        NULL,         -1,                          // COM authentication        NULL,                        // Authentication services        NULL,                        // Reserved        RPC_C_AUTHN_LEVEL_DEFAULT,   // Default authentication         RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation          NULL,                        // Authentication info        EOAC_NONE,                   // Additional capabilities         NULL                         // Reserved        );                          if (FAILED(hres))    {        // Failed to initialize security        if(hres!=RPC_E_TOO_LATE)            return FALSE;    }        hres = CoCreateInstance(        CLSID_WbemLocator,                     0,         CLSCTX_INPROC_SERVER,         IID_IWbemLocator, (LPVOID *) &pLoc);    if (FAILED(hres) || !pLoc)    {        // Failed to create IWbemLocator object.         return FALSE;                   }       hres = pLoc->ConnectServer(         _bstr_t(L"ROOT\\CIMV2"), // Object path of WMI namespace         NULL,                    // User name. NULL = current user         NULL,                    // User password. NULL = current         0,                       // Locale. NULL indicates current         NULL,                    // Security flags.         0,                       // Authority (e.g. Kerberos)         0,                       // Context object          &pSvc                    // pointer to IWbemServices proxy         );        if (FAILED(hres) || !pSvc)    {        // Couldn't conect server        if(pLoc) pLoc->Release();             return FALSE;      }    hres = CoSetProxyBlanket(       pSvc,                        // Indicates the proxy to set       RPC_C_AUTHN_WINNT,           // RPC_C_AUTHN_xxx       RPC_C_AUTHZ_NONE,            // RPC_C_AUTHZ_xxx       NULL,                        // Server principal name        RPC_C_AUTHN_LEVEL_CALL,      // RPC_C_AUTHN_LEVEL_xxx        RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx       NULL,                        // client identity       EOAC_NONE                    // proxy capabilities     );    if (FAILED(hres))    {        // Could not set proxy blanket.        if(pSvc) pSvc->Release();        if(pLoc) pLoc->Release();             return FALSE;                   } 
  • Using Smart pointers

If you often use exclusive object pointers, such as the COM interface, we recommend that you use smart pointers for processing. Smart pointers automatically help you maintain the object reference count and ensure that you do not access the deleted object. In this way, you do not need to care about and control the interface lifecycle. For more information about smart pointers, see smart Pointers-what, why, and which? And implementing a simple smart pointer in C ++.

For example, it is a code that shows how to use the ATL's ccomptr template smart pointer.

#include <windows.h>#include <shobjidl.h> #include <atlbase.h> // Contains the declaration of CComPtr.int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow){    HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED |         COINIT_DISABLE_OLE1DDE);    if (SUCCEEDED(hr))    {        CComPtr<IFileOpenDialog> pFileOpen;        // Create the FileOpenDialog object.        hr = pFileOpen.CoCreateInstance(__uuidof(FileOpenDialog));        if (SUCCEEDED(hr))        {            // Show the Open dialog box.            hr = pFileOpen->Show(NULL);            // Get the file name from the dialog box.            if (SUCCEEDED(hr))            {                CComPtr<IShellItem> pItem;                hr = pFileOpen->GetResult(&pItem);                if (SUCCEEDED(hr))                {                    PWSTR pszFilePath;                    hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);                    // Display the file name to the user.                    if (SUCCEEDED(hr))                    {                        MessageBox(NULL, pszFilePath, L"File Path", MB_OK);                        CoTaskMemFree(pszFilePath);                    }                }                // pItem goes out of scope.            }            // pFileOpen goes out of scope.        }        CoUninitialize();    }    return 0;}  
  • Using = Operator carefully

Let's take a look at the following code;

  CVehicle* pVehicle = GetCurrentVehicle();     // Validate pointer  if(pVehicle==NULL) // Using == operator to compare pointer with NULL     return FALSE;    // Do something with the pointer  pVehicle->Run();

The above code is correct, and the terms pointer detection. However, if "=" is replaced with "=", the following code is used;

 CVehicle* pVehicle = GetCurrentVehicle();   // Validate pointer  if(pVehicle=NULL) // Oops! A mistyping here!     return FALSE;    // Do something with the pointer  pVehicle->Run(); // Crash!!! 

Look at the code above. This mistake will cause the program to crash.

This error can be avoided. You only need to swap the equal signs between the two sides. If you accidentally make this mistake when modifying the code, this error will be detected during program compilation.

 

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.