Embedded Code Security Checks: Choosing Between if and assert?

Follow+Star public number, never miss wonderful content

Embedded Code Security Checks: Choosing Between if and assert?

Source | IOT Internet of Things Town

Do you usually pay attention to code security issues when writing code? Have you taken any action? Today I will share some related content.

1. Introduction

When we are coding, we often need to check the security of the code, for example:

1. Is the pointer null? 2. Is the divisor 0? 3. Is the return result of the function call valid?

4. Was opening a file successful?

The means of checking such boundary conditions are generally using if or assert , regardless of which one is used, it can achieve the goal of checking. So does that mean: these two can be used interchangeably, just use whichever you think of?

In this short article, we will discuss: in different scenarios, should we use if or assert?

When writing this article, I remembered Mr. Kong Yiji’s question: How many ways can the character “茴” in “茴香豆” be written?

It seems that we don’t need to get entangled in how to choose because both can achieve the desired function. In the past, I thought so too, but now I don’t think so.

Becoming a technical expert and getting a better offer may be differentiated by these subtle differences.

2. assert

Just now, I asked a nearby embedded developer who has been working for more than 5 years: How to choose between if and assert? He said: What is assert for?!

It seems necessary to briefly explain assert.

The prototype of assert() is:

void assert(int expression);

1. If the evaluation result of the macro’s parameter is non-zero, do nothing (no action); 2. If the macro’s parameter is zero, print a diagnostic message, 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 does nothing, and the program continues; 2. When b is 0, assert prints an error message and then terminates the program;

Functionally speaking, assert(0 != b); is 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 is the following definition:

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

Since it is a macro definition, it indicates that macro replacement occurs during preprocessing. (For more about macros, you can check out this article: Improving Code Quality with Macros – From Beginner to Abandonment).

From the above definition, we can see:

  1. If the macro NDEBUG is defined, then the assert() macro does nothing, which is equivalent to an empty statement: (void)0;. When compiling code in the release phase, this macro will be defined in the compilation options (Makefile).
  2. If the macro NDEBUG is not defined, then the assert() macro will replace some checking code; during the development phase when executing debug mode compilation, this NDEBUG macro is generally not defined.

3. if VS assert

Let’s describe the problem with a code snippet, as contextualizing makes it easier to understand.

// brief: Concatenate two short strings into one stringchar *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 code above, they will definitely be called in for a talk by their supervisor! It has the following problems:

  1. No validity check on input parameters;
  2. No check on the result of malloc;
  3. Low efficiency of sprintf;

1. Use if statement to check

char *my_concat(char *str1, char *str2){    if (!str1 || !str2)  // Parameter error        return NULL;            int len1 = strlen(str1);    int len2 = strlen(str2);    int len3 = len1 + len2;    char *new_str = (char *)malloc(len3 + 1);        if (!new_str)   // Failed to allocate heap space        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){    // Ensure parameters are correct    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);        // Ensure successful allocation of heap space    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 prefer?

First, let me declare one point: Both of the above two checking methods are common in actual code, and functionally speaking, there seems to be no impact. Therefore, there is no strict right or wrong, much depends on each person’s preferences.

(1) Assert supporters

As the implementer of my_concat(), my goal is to concatenate strings, so the parameters passed in must be valid, and the caller needs to be responsible for this. If invalid parameters are passed, I would be very surprised! What to do: I will crash to show you!

(2) If supporters

I write the my_concat() function to be robust, and I anticipate that the caller might mess up, deliberately passing in some invalid parameters to test my coding skills. No worries, come on, I can handle any situation!

Both factions seem to have solid reasons! So how should we choose? Should we really just go with our feelings?

Let’s assume that we strictly follow the conventional process to develop a project:

1. In the development phase, the NDEBUG macro is not defined in the compilation options, then assert works; 2. When the project is released, the NDEBUG macro is defined in the compilation options, then assert is equivalent to an empty statement;

This means that only in the debug development phase, using assert can correctly check for invalid parameters. In the release phase, assert does not work, and if the caller passes invalid parameters, the program will only crash.

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 a unit test that was not written well, failing to catch the invalid parameter case!

4. The essence of assert

Assert is for validity verification, its main purpose is: to make our program crash as much as possible during the development phase. Every crash indicates that there is a bug in the code that needs to be fixed.

When we write an assert statement, it means that: the situation where the assertion fails is not allowed. We must ensure that the assertion succeeds for the program to continue executing.

5. The essence of if-else

If-else statements are used for logical processing; they are meant to handle various possible situations. This means: each branch is reasonable and allowed, and we need to handle these branches.

6. My preferred version

char *my_concat(char *str1, char *str2){    // Parameters must be valid    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);        // The possibility of heap allocation failure is reasonable and allowed.    if (!new_str)        return NULL;        memset(new_str, 0 len3 + 1);    sprintf(new_str, "%s%s", str1, str2);    return new_str;}

For parameters: I believe that the parameters passed in must be valid. If invalid parameters appear, it indicates that there is a bug in the code, not allowed to occur, and must be resolved.

For resource allocation results (malloc function): I believe that resource allocation failure is reasonable, possible, and allowed, and I have also handled this situation.

Of course, it is not to say that parameter checks must use assert; it mainly depends on different scenarios and semantics. For example, in the following case:

int g_state;void get_error_str(bool flag){    if (TRUE == flag)    {        g_state = 1;        assert(1 == g_state); // Ensure assignment success    }    else    {        g_state = 0;        assert(0 == g_state); // Ensure assignment success    }}

The flag parameter represents different branch situations, and after assigning to g_state, we must ensure the correctness of the assignment result, so we use assert.

4. Conclusion

This article analyzes a relatively obscure and vague concept in C language, which seems somewhat abstract, but indeed requires us to stop and think carefully.

If there are some situations that I really can’t grasp, I will ask myself a question:

Is this situation allowed to occur?

Not allowed: use assert to catch all error situations during the development phase;

Allowed: use if-else, indicating that this is a reasonable logic that needs further processing.

Statement: The materials for this article come from the internet, and the copyright belongs to the original author. If there are any copyright issues, please contact me to delete.
———— END ————
Embedded Code Security Checks: Choosing Between if and assert?
● Column “Embedded Tools”
● Column “Embedded Development”
● Column “Keil Tutorial”
● Selected Tutorials from Embedded Column
Follow the public account reply “Join Group” to join the technical exchange group according to the rules, reply “1024” to see more content.
Embedded Code Security Checks: Choosing Between if and assert?
Embedded Code Security Checks: Choosing Between if and assert?
Click “Read Original” for more sharing.

Leave a Comment