前言
最近用man手册查看API函数时,常见例子中含有do{…}while(0)奇怪的用法。为什么要用do{…}while(0)呢?就是能够实现复杂的定义,通用性强,任何情况下都适用。
do{…}while(0)的妙用
下面do{…}while(0)是Linux内核中常见用在错误的处理上:
#define handle_error_en(en, msg) \
do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)
使用do{….}while(0) 把它包裹起来,成为一个独立的语法单元,从而不会与上下文发生混淆。同时因为绝大多数的编译器都能够识别do{…}while(0)这种无用的循环并进行优化,所以使用这种方法也不会导致程序的性能降低。另外,do…while一定会执行一次,不论while中的语句是什么。
完整代码:
#include <pthread.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#define handle_error_en(en, msg) \
do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)
struct thread_info {
pthread_t thread_id; /* ID returned by pthread_create() */
int thread_num;
char *argv_string;
};
static void *thread_start(void *arg)
{
struct thread_info *tinfo = arg;
char *uargv, *p;
printf("Thread %d: top of stack near %p; argv_string=%s\n",
tinfo->thread_num, &p, tinfo->argv_string);
uargv = strdup(tinfo->argv_string);
if (uargv == NULL)
handle_error("strdup");
for (p = uargv; *p != '\0'; p++)
*p = toupper(*p);
return uargv;
}
int main(int argc, char *argv[])
{
int s, tnum, opt, num_threads;
struct thread_info *tinfo;
pthread_attr_t attr;
int stack_size;
void *res;
/* “-s”选项指定线程的堆栈大小 */
stack_size = -1;
while ((opt = getopt(argc, argv, "s:")) != -1)
{
switch (opt) {
case 's':
stack_size = strtoul(optarg, NULL, 0);
break;
default:
fprintf(stderr, "Usage: %s [-s stack-size] arg...\n",
argv[0]);
exit(EXIT_FAILURE);
}
}
num_threads = argc - optind;
/* 初始化线程创建属性 */
s = pthread_attr_init(&attr);
if (s != 0)
handle_error_en(s, "pthread_attr_init");
if (stack_size > 0) {
s = pthread_attr_setstacksize(&attr, stack_size);
if (s != 0)
handle_error_en(s, "pthread_attr_setstacksize");
}
/* 为pthread_create()参数分配内存 */
tinfo = calloc(num_threads, sizeof(struct thread_info));
if (tinfo == NULL)
handle_error("calloc");
/* 为每个命令行参数创建一个线程 */
for (tnum = 0; tnum < num_threads; tnum++) {
tinfo[tnum].thread_num = tnum + 1;
tinfo[tnum].argv_string = argv[optind + tnum];
/* 线程的创建,pthread_create()调用将线程ID存储到tinfo[]对应元素 */
s = pthread_create(&tinfo[tnum].thread_id, &attr,
&thread_start, &tinfo[tnum]);
if (s != 0)
handle_error_en(s, "pthread_create");
}
/* 销毁线程属性对象,因为它不是不再需要 */
s = pthread_attr_destroy(&attr);
if (s != 0)
handle_error_en(s, "pthread_attr_destroy");
/* 线程等待, 并显示其返回值 */
for (tnum = 0; tnum < num_threads; tnum++) {
s = pthread_join(tinfo[tnum].thread_id, &res);
if (s != 0)
handle_error_en(s, "pthread_join");
printf("Joined with thread %d; returned value was %s\n",
tinfo[tnum].thread_num, (char *) res);
free(res);
}
free(tinfo);
exit(EXIT_SUCCESS);
}
输出结果:
也就是说,为什么Linux内核代码要这样来做,这是因为内核代码采用do{}while(0);这种结构可以保证无论在什么地方都可以正确的执行一次 ,这就是它用得最妙的地方。