Do you only use do-while to implement loops? Such a waste!


This is Brother Dao's 010th original

Preface

The knowledge points explained in this article are very small, but it is very applicable in some programming situations. You can taste this short article as a dessert .

Everyone on earth knows that the do-while statement is a loop statement in C/C++. Its characteristics are:

The body of the loop is executed at least once;
the end condition is judged at the end of the loop.

In fact, do-while can also be used in other occasions to deal with some of your problems very cleverly , such as:

Write complex statements in the macro definition;
abort the processing of the code segment in the function body.

It seems a bit abstract, so let's be more specific and talk about these usages through code.

It is also strongly recommended that you use these tips in ordinary projects. Imitation is the first step. First rigidify-then optimize-finally solidify . This is the most effective way to improve your programming ability.
Over time and more use, these things belong to you.

Magical use in macro definition

Wrong macro definition

// 目的:把两个参数分别自增一下
#define OPT(a, b)   a++; b++;

int main(int argc, char *argv[])
{
    int i = 1;
    int j = 1;
    OPT(i, j);
    printf("i = %d, j = %d \n", i, j);
    return 0;
}

Test it, the result is no problem (the purpose of the code is to make the two variables i and j increase by 1):

i = 2, j = 2

Moreover OPT(i, j);, the final semicolon can also be omitted, and there is no problem with compilation and results.

But it is estimated that no one would use macros in such a project, right? ! Take a look at the following example:
add an if condition judgment to the outer layer of the OPT macro :

#define OPT(a, b)   a++; b++;

int main(int argc, char *argv[])
{
    int i = 1;
    int j = 1;
    if(0)
        OPT(i, j);
    printf("i = %d, j = %d \n", i, j);
    return 0;
}

The print result is:

i = 1, j = 2

The problem has arisen: Our original intention is that the if condition is false, and neither of these two variables should be incremented, but the output result is: the second parameter is incremented .

In fact, the problem is obvious, and the macro expansion is clear at a glance.

if(0)
    a++; b++;

The reason for the error is clear at a glance: because the if statement does not wrap all the code that needs to be executed with braces {}, only the a++;statement is in ifthe control range of the b++;statement , and the statement is executed anyway.

Maybe you would say that this is simple, ifyou must add braces {} when you use it. The reason is right, if this macro definition is only used by you, this is not a problem. But if the macro definition is written by you and the user is your colleague, then how do you require others to code in the format specified by you? After all, everyone's habits are different.

In many cases, it is unrealistic to ask others. A more effective method is to optimize your output and provide safer code so that others will have no chance to make mistakes.

Better macro definition

What can be done to be safer? Is it more versatile? Use do-while !

#define OPT(a, b)   do{a++;b++;}while(0)

In other words, as long as there are multiple statements in the macro definition, you can use do-while to wrap all of these statements, so that no matter how you use this macro, there will be no problems.

E.g:

if(0)
    OPT(i, j);

The code after macro expansion is:

if(0)
    do {
        a++;
        b++;
    }while(0);

If you add curly braces to the if, it will be better visually:

if(0) {
    OPT(i, j);
}

The code after macro expansion is:

if(0) {
    do {
        a++;
        b++;
    }while(0);
}

It can be seen that no matter whether the braces {} are added, there is no problem in terms of syntax and semantics.

Here is another small detail to pay attention to: In OPT(i,j);this line of code, a semicolon is added to the end .

If there is no semicolon, the code after the macro expansion is:

if(0)
    do {
        a++;
        b++;
    }while(0) // 注意:这里没有分号

Because while(0) does not have a semicolon, compilation errors will occur . In order not to make a request to the user of the macro, you can add a semicolon at the end of the macro, as follows:

#define OPT(a, b)   do{a++;b++;}while(0);

Summary: Use the do-while statement to wrap the multi-line statement in the macro definition, which solves the security problem of the macro definition.

However, nothing can be perfect, for example: using do-while in a macro definition cannot return a result.

That is to say: if we need to return a result from the macro definition , then do-while is useless. What should I do?

Another nice macro definition

If the macro definition needs to return a result, the best way is to use ({...})to wrap multiple lines of statements in the macro definition. as follows:

#define ADD(a, b, c) ({ ++a; ++b; c=a+b; })

int i = 1;
int j = 2;
int k;
printf("k = %d \n", ADD(i, j, k));

The following picture is from the official GNU documentation:

The translation is:

In GNU C, it is legal to write complex statements in parentheses (), so you can use loops, switches, and local variables in an expression.
What is a complex sentence? It is a multi-line statement surrounded by curly braces {}.
In the above example, the parentheses should be placed outside the braces.

Use the ({...})definition macro, because it is a multi-line statement, it can return a result, which is better than do-while.

Since the use of local variables in macro definitions is mentioned here, we will provide a little trick to improve code execution efficiency.

Take a look at this macro definition:

#define max(a,b) ({ (a) > (b) ? (a) : (b) })

float i = 1.234;
float j = 4.321;
float max = max((i / 0.8 + 5) / 3, (j * 0.8) / 1.5);

After macro expansion, one of a or b must be calculated twice . Of course, the example here is relatively simple and does not reflect the difference. If it is an occasion with very demanding time requirements and a large amount of calculations, then the time consumed by the two calculations in this macro must be considered. How to optimize it? Use local variables !

#define max(a,b)  ({ int _a = (a), _b = (b); _a > _b ? _a : _b; })

By adding local variables _a and _b to cache the calculation results , the problem of two calculations is eliminated.

This example can continue to be optimized. The local variable type here is int, which is hard-coded. Only two integer variables can be compared. If written like this:

#define max(a, b)  ({ typeof(a) _a = (a), _b = (b); _a > _b ? _a : _b; })

That is, typeof is used to dynamically obtain the type of the comparison variable, so that any numeric variable can use this macro.

For the description of typeof, please see this picture of GNU. In the reference link at the end of the article, you can see a more detailed official description.

Magical use in function body

Let's look at 2 pieces of code first.

Function function: return the error string corresponding to the error code

char *get_error_msg(int err_code)
{
    if (1 == err_code) {
        return "invalid name";
    } else if (2 == err_code) {
        return "invalid password";
    } else if (3 == err_code) {
        return "network error";
    }
    
    return "unkown error";
}

Thinking: A well-designed function has only one exit, that is, the return statement, but this function has so many return statements. Does it seem messy? The sample code is so small that it doesn't seem to feel anything. But hundreds of lines of functions are still relatively common in projects. In this case, if you give you more than a dozen return statements , would you want to slap the guy who wrote the code?

Function: connect to the server via TCP Socket

void connect_server(char *ip, int port)
{
    int ret, sockfd;
    sockfd = socket(...);
    if (sockfd < 0) {
        printf("socket create failed! \n");
        goto end;
    }

    ret = connect(sockfd, ...);
    if (ret < 0) {
        printf("connect failed! \n");
        goto end;
    }

    ret = send(sockfd, ...)
    if (ret < 0) {
        printf("send failed! \n");
        goto end;
    }

end:
    其他代码
}

Thinking: In TCP socket programming, multiple system functions need to be called in a fixed order . After calling the system function in this code, the result is checked, which is a very good habit. If an error occurs in a certain call, the following operations need to be aborted for error handling. Although the use of goto statements is not prohibited in the C language, is there no beautiful and more elegant way to see so many gotos?

Summarizing the above two pieces of code, their common features are:

In a series of statements, only a part of the statement needs to be executed, that is, execution is aborted from a certain middle position of the code block.

To abort execution, the first thing we think of is the break keyword, which is mainly used in loops and switch statements. The do-while loop statement executes the loop body first, and then judges the loop at the end. Then you can use this to solve the problems faced by these two pieces of code.

Solve the problem of multiple returns

char *get_error_msg(int err_code)
{
    char *msg;
    do {
        if (1 == err_code) {
            msg = "invalid name";
            break;
        } else if (2 == err_code) {
            msg = "invalid password";
            break;
        } else if (3 == err_code) {
            msg = "network error";
            break;
        } else {
            msg = "unkown error";
            break;
        }
    }while(0);
    
    return msg;
}

Solve the problem of goto

void connect_server(char *ip, int port)
{
    int ret, sockfd;
    do {
        sockfd = socket(...);
        if (sockfd < 0) {
            printf("socket create failed! \n");
            break;
        }

        ret = connect(sockfd, ...);
        if (ret < 0) {
            printf("connect failed! \n");
            break;
        }

        ret = send(sockfd, ...)
        if (ret < 0) {
            printf("send failed! \n");
            break;
        }
    }while(0);

    其他代码
}
Does this kind of code look more pleasing to the eye?

to sum up

The main function of do-while is loop processing, but in this article, the point we use is not the loop function, but the wrapping of code blocks and the function of suspending execution. These small points are very common in some awesome open source codes. We have to learn, imitate, and use when we see it. If you use it too much, it will be yours!

Have you started to like do-while statements?

Reference documents:

[1] https://gcc.gnu.org/onlinedocs/gcc/Typeof.html
[2] https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html
[3] https: //stackoverflow.com/questions/9495962/why-use-do-while-0-in-macro-definition
[4] https://gcc.gnu.org/onlinedocs/gcc-6.2.0/gcc/Statement- Exprs.html#Statement-Exprs


[Original Statement]

> OF: Columbia Road (public No.: The IOT of IOT town )
> know almost: Columbia Road
> B station: Columbia Road Share
> Nuggets: Columbia Road Share
> CSDN: Columbia Road Share

If you think the article is good, please forward it and share it with your friends.


I will summarize and share the actual combat experience of projects in embedded development for more than ten years , I believe you will not be disappointed!

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.
Press and hold the QR code in the picture to follow, each article has dry goods.




<
Recommended reading

[1] The underlying debugging principle of gdb is so simple
[2] Double buffering technology in producer and consumer mode
[3] In- depth LUA scripting language, so that you can thoroughly understand the principle of debugging
[4] Step by step analysis-how to implement it in C Object-Oriented Programming

Guess you like

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