do while(0)的作用

  我们经常可以在一些优秀开源代码的头文件里发现一些宏定义使用了do {} while(0)语句,也许你会疑惑do {} while(0)中的代码不就是只执行一次吗,为什么还要多此一举使用do {} while(0)循环结构去包裹呢?实际上,do {} while(0)的作用很大,下面可以看几个例子。

一、定义复杂宏避免逻辑或编译错误

  假如你定义了一个宏,这个宏的作用为连续调用2个函数,宏定义如下:

#define DOSOMETHING()   func1(); func2()

  但使用时可能会出现以下这样的情况:

//展开前:
if(judge == TRUE)
    DOSOMETHING;
 
//展开后:
if(judge == TRUE)
    func1();
    func2();

  可以看到,由于没有加上括号,func2是必会执行的,已经引起了逻辑错误;如果这个if还有else等分支的话,会出现编译错误。为了避免这个错误,也许你会给宏加上括号,就像下面这样:

#define DOSOMETHING()   { func1(); func2();}

  使用时再次展开宏:

//展开前:
if(judge == TRUE)
    DOSOMETHING;
 
//展开后
if(judge == TRUE)
{   
    func1();
    func2();
}

  可以看到,由于调用DOSOMETHING宏时,后面有个“;”号,所以展开后最后多了一个“;”号,显然如果这个符号是多余的,虽然有些编译器可以忽略多余的“;”,但如果这个if后面有个else语句,也会引起编译的异常。也许你会说,最后的“;”是多余的,那我调用的时候不加“;”不就可以了吗,就像下面这样。

if(judge == TRUE)
    DOSOMETHING

  当然这是行得通的, 但是在每个语句后面加分号是一种约定俗成的习惯,应当极力避免这种使用方式,否则阅读代码将是一件痛苦的事情!这时候do {} while(0)就派上用场了!我们可以用do {} while(0) 重新定义DOSOMETHING宏:

#define DOSOMETHING()   do{ func1(); func2();} while(0)

  再次展开宏,就可以看到,使用上就毫无问题了! do {} while(0)真香啊!

//展开前:
if(judge == TRUE)
    DOSOMETHING;
 
//展开后:
if(judge == TRUE)
    do{
        func1();
        func2();
    }while(0);

二、代替goto语句完成函数退出逻辑

  虽然尽量不使用goto是C语言开发中不成文的规定,但实际上我们经常使用goto语句来完成一些清理工作:

int func()
{
    bool error = false;
    void *ptr = malloc(1024);
 
    error = dosomething1();
    if(error)
        goto exit;
    error = dosomething2();
    if(error)
        goto exit;
exit:
    free(ptr);
    return 0;
}

  我们可以使用do {} while(0)结构来代替这种goto的用法,避免goto语句的使用:

int func()
{
    bool error = false;
    void *ptr = malloc(1024);
 
    do{
        error = dosomething1();
        if(error)
            break;
        error = dosomething2();
        if(error)
            break;
    }while(0);
 
    free(ptr);
    return 0;
}
  • 在学习手写线程池时,初始化一个线程池的操作中,也使用到了do…while(0)的这种小技巧:
      用malloc给线程池pool申请堆内存,并给线程池中的工作的线程ID(threadIDs)和任务队列(taskQ)分别分配内存。若其中一个分配堆区内存失败,则需要返回并释放掉前序步骤已经分配的堆区内存。如果不用do…while(0),则每个分配内存失败返回之前都要释放之前分配的内存,略显麻烦。用了do…while(0)后,可在函数体内部使用break,跳出剩余步骤,直接到判断是否需要释放内存的语句,使整个代码流程更清晰。
//初始化线程池
ThreadPool* threadPoolCreate(int min, int max, int queueSize)
{
    ThreadPool* pool = (ThreadPool*)malloc(sizeof(ThreadPool));
    do 
    {
        if (pool == NULL)
        {
            printf("malloc threadpool fail...\n");
            break;
        }

        pool->threadIDs = (pthread_t*)malloc(sizeof(pthread_t) * max);
        if (pool->threadIDs == NULL)
        {
            printf("malloc threadIDs fail...\n");
            break;
        }
        //threadIDs对应堆区内存创建成功,用memset()指定threadIDs初始值全为0
        memset(pool->threadIDs, 0, sizeof(pthread_t) * max);
        
        pool->minNum = min;
        pool->maxNum = max;
        pool->busyNum = 0;
        pool->liveNum = min;    // 和最小个数相等
        pool->exitNum = 0;

        //初始化互斥锁和条件变量的返回值若不为0则说明初始化失败
        if (pthread_mutex_init(&pool->mutexPool, NULL) != 0 ||
            pthread_mutex_init(&pool->mutexBusy, NULL) != 0 ||
            pthread_cond_init(&pool->notEmpty, NULL) != 0 ||
            pthread_cond_init(&pool->notFull, NULL) != 0)
        {
            printf("mutex or condition init fail...\n");
            break;
        }

        // 任务队列
        pool->taskQ = (Task*)malloc(sizeof(Task) * queueSize);
        pool->queueCapacity = queueSize;
        pool->queueSize = 0;
        pool->queueFront = 0;
        pool->queueRear = 0;

        pool->shutdown = 0;

        // 创建线程
        pthread_create(&pool->managerID, NULL, manager, pool);
        for (int i = 0; i < min; ++i)
        {
            pthread_create(&pool->threadIDs[i], NULL, worker, pool);
        }
        return pool;
    } while (0);

    // 释放资源
    if (pool && pool->threadIDs) free(pool->threadIDs);
    if (pool && pool->taskQ) free(pool->taskQ);
    if (pool) free(pool);

    return NULL;
}
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值