تابع ()perror

اکثر توابع Network API هنگامی که با خطایی مواجه می شوند، یک متغییر global به نام errno را مقدار دهی میکنند که این متغییر توسط تابع ()perror به error message های قابل نمایش تبدیل میشود:

#include <stdio.h>
#include <errno.h>
 
void perror(const char *s);  

این تابع ابتدا رشته s و سپس پیغام مربوط به آخرین خطای رخ داده را نمایش میدهد.

در عمل چون تعداد فراخوانی های تابع ()perror زیاد است، برای فراخوانی آن از یک تکنیک بسیار جالب استفاده می شود. ابتدا یک ماکرو به شکل زیر تعریف شده :

// man 2 bind
#define handle_error(msg) \
    do { perror(msg); exit(EXIT_FAILURE); } while (0)

و سپس در برنامه با این شکل استفاده می شود :

if (sockfd == -1)
    handle_error("socket");

برای توضیح وجود do{….}while(0) در ماکرو باید گفت که این تکنیک کمک میکند این ماکرو در شرایط مختلف قابل استفاده و درست باشد. در مثال زیر :

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <errno.h>
#include <unistd.h>
 
#define handle_error(msg) \
    do { perror(msg); exit(EXIT_FAILURE); } while(0)
 
int main()
{
    int sockfd;
 
    sockfd = socket(0, 0, 0);
 
    if (sockfd == -1)
        handle_error("socket");
    else
        printf("I got a socket\n");
 
    return 0;
}

اگر ماکرو به این شکل تعریف شود :

#define handle_error(msg) \
    perror(msg); exit(EXIT_FAILURE);

کد توسط preprocessor به این شکل تبدیل شده و به دلیل فاصله بین if و else ترجمه نخواهد شد :

    if (sockfd == -1)
        perror(msg); exit(EXIT_FAILURE);
    else
        printf("I got a socket\n");

حتی اگر از { } کمک بگیریم و به این شکل ماکرو را تعریف کنیم :

#define handle_error(msg) \
    { perror(msg); exit(EXIT_FAILURE); }

این بار کد توسط preprocessor به شکل زیر تبدیل شده و بخاطر ; بعد از { باز هم بین if و else فاصله ایجاد شده و ترجمه نخواهد شد :

    if (sockfd == -1)
        { perror(msg); exit(EXIT_FAILURE); };
    else
        printf("I got a socket\n");

غیر از روش do{….}while(0) با کمک if(1){….}else و با کمک comma operator هم می توان این مشکل را حل کرد. البته comma operator بسیار محدود است و در موقعیتهای ساده کار آمد خواهد بود :

#define handle_error(msg) \
    if(1) { perror(msg); exit(EXIT_FAILURE); } else
 
#define handle_error(msg) \
    perror(msg), exit(EXIT_FAILURE)

برای بررسی خروجی preprocessor می توان از E- در gcc استفاده کرد :

gcc -E file.c