Follow+Star Public Account Number, don’t miss out on exciting content
Source | IOT Internet of Things Town
When you write code, do you pay attention to the security issues of the code? What actions do you take? 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 zero?3. Is the return value of the function call valid?
4. Was the file opened successfully?
The means of checking these types of boundary conditions are generally using if or assert, and either can achieve the purpose of checking. So does this mean that: these two can be used interchangeably, whichever comes to mind can be used?
In this short article, we will discuss: in different scenarios, should we use if, or should we use assert?
While writing this article, I remembered a question from the old gentleman Confucius: How many ways can the character “茴” in “茴香豆” be written?
It seems we don’t need to get too hung up on how to choose, as both can achieve the desired functionality. I used to think so, but now I don’t think that way.
Becoming a technical expert and getting a better offer may depend on these subtle differences.
2. assert
Just now, I asked a colleague who has been an embedded developer for over 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 evaluated 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 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 does nothing, and the program continues; 2. When b is 0, assert prints an error message and then terminates the program;
Functionally, <span>assert(0 != b);</span>
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 on macros, you can refer to this article: Enhancing Code Quality with Macros – From Beginner to Abandonment).
From the above definition, we can see:
- If the macro NDEBUG is defined, then the assert() macro does nothing, which is equivalent to an empty statement:
<span>(void)0;</span>
, and during the release phase of compiling the code, this macro is usually defined in the compilation options (Makefile).- If the macro NDEBUG is not defined, then the assert() macro will replace some checking code, and during the development phase when compiling in debug mode, we generally do not define this NDEBUG macro.
3. if VS assert
Let’s describe the problem with a code snippet, as it is easier to understand through a scenario.
// 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 above code, they will definitely be called in by their supervisor! It has the following issues:
- No validity check on input parameters;
- No check on the result of malloc;
- sprintf is very inefficient;
- …
1. Using if statements 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. Using 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 heap space allocation is successful 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 clarify: both of these checking methods are common in actual code, and functionally, there seems to be no impact. Therefore, there is no strict right or wrong, and much depends on individual preferences.
(1) Supporters of assert
As the implementer of <span>my_concat()</span>
, my goal is to concatenate strings, so the parameters passed in must be valid. 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) Supporters of if
I write the <span>my_concat()</span>
function to be very robust, and I anticipate that the caller will mess up and deliberately pass in some invalid parameters to test my coding skills. No problem, bring it on, I can handle any situation!
Both sides seem to have valid reasons! So how should we choose? Should we really just go with our feelings?
Assuming we strictly follow the conventional process to develop a project:
1. During the development phase, the NDEBUG macro is not defined in the compilation options, so assert will take effect;2. When the project is released, the NDEBUG macro is defined in the compilation options, so assert becomes equivalent to an empty statement;
This means that only during the debug development phase can assert correctly check for invalid parameters. In the release phase, assert does not take effect, and if the caller passes invalid parameters, the program is doomed to 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 fundamentally a failure of unit testing, as the invalid parameter case was not caught!
4. The essence of assert
Assert is meant to validate validity, and its greatest function 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 indicates that: the failure of the assertion is not allowed, and must be guaranteed to succeed for the program to continue executing.
5. The essence of if-else
If-else statements are used for logical processing, 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 case of failed heap space allocation is possible 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 the parameters passed in must be valid, and if invalid parameters appear, it indicates a bug in the code, which is not allowed 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’s not 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 a bit ethereal, but indeed requires us to stop and think carefully.
If there are some scenarios where it is difficult to grasp, I will ask myself a question:
Is this situation allowed to occur?
Not allowed: use assert to find all error cases as much as possible during the development phase;
Allowed: use if-else, indicating that this is a reasonable logic that needs to be processed further.
Disclaimer:This article’s material comes from the internet, and the copyright belongs to the original author. If there are any copyright issues, please contact me for deletion.———— END ————● Column “Embedded Tools”● Column “Embedded Development”● Column “Keil Tutorials”● Selected Tutorials from Embedded ColumnFollow the public accountReply “Add Group” to join the technical exchange group according to the rules, reply “1024” to see more content.Click “Read the original text” to see more shares.