Code safety and robustness: how to choose between if and assert?


Brother Dao's original 023

I. Introduction

When we press the code, we often need to check the safety of the code, for example:

  1. Is the pointer empty?
  2. Is the dividend 0?
  3. Is the return result of the function call valid?
  4. Is it successful to open a file?

The means to check this type of boundary conditions are generally to use if or assert assertions , no matter which one is used, the check can be achieved. So does it mean: these two can be used casually, and whichever comes to mind?

In this short essay, let’s talk about it: In different scenarios, should I use if or should I use assert?

When writing this article, I remembered the question of Mr. Kong Yiji : How many ways are there to write the word "fennel" for fennel beans?

It seems that we don't have to worry about how to choose, because it can achieve the desired function. Before I feel the same way, but now I do not think so .

Become a technical expert and get a better offer, maybe it's just between these subtleties that the winner will be the winner.

Two, assert assertion

Just now, I asked an embedded developer next to me who has worked for more than 5 years: How to choose if and assert ? He said: What does assert do? !

It seems that it is necessary to briefly talk about assert assertion first.

The prototype of assert() is:

void assert(int expression);

  1. If the evaluation result of the macro parameter is non-zero, no action is taken;
  2. If the macro parameter is zero, print a diagnostic message, and then call abort().

For example, the following code:

#include <assert.h>
int my_div(int a, int b)
{
    assert(0 != b);
    return a / b;
}
  1. When b is not 0, assert asserts that nothing is done and the program executes;
  2. When b is 0, the assert assertion will print an error message, and then terminate the program;

Functionally, it is assert(0 != b);equivalent to the following code:

if (0 == b)
{
    fprintf(stderr, "b is zero...");
    abort();
}

assert is a macro, not a function

In the assert.h header file, there are the following definitions:

#ifdef NDEBUG
    #define assert(condition) ((void)0)
#else
    #define assert(condition) /*implementation defined*/
#endif

Since it is a macro definition, it means that macro replacement is performed during preprocessing . (For more information on macros, you can read this article: A weapon to improve code compulsion: macro definition-from entry to abandonment ).

As can be seen from the above definition:

  1. If the macro NDEBUG is defined, the assert() macro will not do anything, which is equivalent to an empty statement: (void)0;When the code is compiled in the release phase, this macro will be defined in the makefile.
  2. If the macro NDEBUG is not defined, then the assert() macro will replace some check codes. When we execute the debug mode compilation during the development phase, the NDEBUG macro will generally be blocked.

三、if VS assert

It is easier to understand the problem with a code snippet, and to discuss it in a scene .

// brief: 把两个短字符串拼接成一个字符串
char *my_concat(char *str1, char *str2)
{
    int len1 = strlen(str1);
    int len2 = strlen(str2);
    int len3 = len1 + len2;
    char *new_str = (char *)malloc(len3 + 1);
    memset(new_str, 0 len3 + 1);
    sprintf(new_str, "%s%s", str1, str2);
    return new_str;
}

If a developer writes the above code, he will definitely be interviewed by the leader! It has the following problems:

  1. No validity check of input parameters;
  2. The results of malloc are not checked;
  3. The efficiency of sprintf is very low;

1. Use if statement to check

char *my_concat(char *str1, char *str2)
{
    if (!str1 || !str2)  // 参数错误
        return NULL;
        
    int len1 = strlen(str1);
    int len2 = strlen(str2);
    int len3 = len1 + len2;
    char *new_str = (char *)malloc(len3 + 1);
    
    if (!new_str)   // 申请堆空间失败
        return NULL;
    
    memset(new_str, 0 len3 + 1);
    sprintf(new_str, "%s%s", str1, str2);
    return new_str;
}

2. Use assert to check

char *my_concat(char *str1, char *str2)
{
    // 确保参数正确
    assert(NULL != str1);
    assert(NULL != str2);
    
    int len1 = strlen(str1);
    int len2 = strlen(str2);
    int len3 = len1 + len2;
    char *new_str = (char *)malloc(len3 + 1);
    
    // 确保申请堆空间成功
    assert(NULL != new_str);
    
    memset(new_str, 0 len3 + 1);
    sprintf(new_str, "%s%s", str1, str2);
    return new_str;
}

3. Which one do you like?

First of all, make a statement : the above two check methods are very common in actual code, and they seem to have no effect in terms of function. Therefore, there is no strict distinction between wrong and right , and many of them depend on different preferences and habits of each person.

(1) assert supporters

I, as my_concat()implementers function, the purpose is to concatenate strings, then the argument passed must be valid , the caller needs to be responsible for it. If the parameters passed in are invalid, I will be very surprised ! What to do: show you the crash!

(2) if supporters

I wrote my_concat()a function very strong , I would expect that the caller would fucks, deliberately passed some invalid arguments, to test my code level. It's okay, come on, I can handle any situation !

The reasons for these two factions seem to be sufficient! How to choose? Do you really follow your feelings?

Suppose we develop a project in strict accordance with the conventional process:

  1. In the development stage, if the macro NDEBUG is not defined in the compilation options, then assert will work;
  2. When the project is released, NDEBUG is defined in the compilation options to change the macro, then assert is equivalent to an empty statement;

In other words, only in the debug development stage , the assert can be used to correctly check that the parameter is invalid. In the release phase, assert does not work . If the caller passes invalid parameters, then the program has the fate of crashing.

What does this indicate? Is there a bug in the code? Or is the code not robust enough?

From my personal understanding, this is simply the case where the unit test was not written well and the parameters were not detected to be invalid!

4. The essence of assert

Assert is to verify the validity , and its biggest role is to make our program crash as much as possible during the development phase . Every crash means that there are bugs in the code and we need to fix them.

When we write down an assert assertion, it means that the failure of the assertion is not allowed and is not allowed . Must ensure that the assertion is successful, the program can continue to execute.

5. The essence of if-else

The if-else statement is used for logical processing , it is to deal with various possible situations. That is to say: every branch is reasonable and allowed to appear, and we have to deal with these branches.

6. My favorite version

char *my_concat(char *str1, char *str2)
{
    // 参数必须有效
    assert(NULL != str1);
    assert(NULL != str2);
    
    int len1 = strlen(str1);
    int len2 = strlen(str2);
    int len3 = len1 + len2;
    char *new_str = (char *)malloc(len3 + 1);
    
    // 申请堆空间失败的情况,是可能的,是允许出现的情况。
    if (!new_str)
        return NULL;
    
    memset(new_str, 0 len3 + 1);
    sprintf(new_str, "%s%s", str1, str2);
    return new_str;
}

Regarding parameters : I think the incoming parameters must be valid. If an invalid parameter appears, it means that there is a bug in the code. Such a situation is not allowed and must be resolved.

Regarding the result of resource allocation (malloc function): I think the resource allocation failure is reasonable, possible, and allowed , and I also dealt with this situation.

Of course, it doesn't mean that assert is needed for parameter checking, it is mainly judged according to different scenarios and semantics. For example, the following example:

int g_state;
void get_error_str(bool flag)
{
    if (TRUE == flag)
    {
        g_state = 1;
        assert(1 == g_state); // 确保赋值成功
    }
    else
    {
        g_state = 0;
        assert(0 == g_state); // 确保赋值成功
    }
}

The flag parameter represents different branch situations, and after assigning to g_state, the correctness of the assignment result must be guaranteed , so assert is used.

Five, summary

This article analyzes a relatively obscure and vague concept in the C language , which seems a bit illusory, but it does require us to stop and think carefully .

If some scenes are really bad, I will ask myself a question :

Is this situation allowed?

Not allowed : just use assert and try to find all error conditions during the development phase;

Allowed : Just use if-else to show that this is a reasonable logic and needs to be processed in the next step.


[Original Statement]

Reprint: Welcome to reprint, but without the consent of the author, this statement must be retained, and the original link must be given in the article.



Recommended reading

C language pointer-from the underlying principles to fancy skills, with pictures and codes to help you explain a thorough
step by step analysis-how to use C to achieve object-oriented programming
my favorite method of communication between processes-message bus
IoT gateway development: based on The design process of the MQTT message bus (on)
is a powerful tool to improve the code: macro definition-from entry to abandon the
original gdb's underlying debugging principle is so simple.
Use setjmp and longjmp in the C language to achieve exception capture and coroutine
encryption, Those things about the certificate
go deep into the LUA scripting language, so you can thoroughly understand the principle of debugging

Guess you like

Origin blog.csdn.net/u012296253/article/details/114180334