Image

Category:

Я хотел бы с вами поделиться той информацией, что сам узнал совсем недавно и которая показалась мне очень занятной. Если вдруг вам неинтересно программирование под Линукс и вы не знаете, что такое компилятор gcc, то можете не дочитывать этот пост до конца — там ничего интересного ;)

Я хочу рассказать про несколько расширений компилятора gcc, которые могут быть очень полезны программисту, ценящему качество кода и не игнорирующему предупреждения друга-компилятора. Первый из них это...

Атрибут unused.



У меня в Сизифе есть несколько пакетов, которые я хотел бы собирать с флагами -Wall -W -Werror, но вот беда: gcc жалуется, что аргумент в функции не используется. Будь всё просто, я бы удалил этот аргумент и всё, но загвоздка в том, что эта функция обязательно должна принимать аргумент, даже если она его и не использует, к примеру, в том случае, если это обработчик сигнала. Знакома ситуация? До сих пор я не знал, что и делать: то ли отключить генерацию предупреждений о неиспользуемых аргументах, то ли отказаться от идеи с -W -Werror.

Вот простой и рабочий пример программы с сигналами, которая выдаст предупреждение будучи скомпилированной с флагами -W -Wall:

#include <stdio.h>
#include <stdlib.h>

#include <sys/types.h>
#include <unistd.h>
#include <signal.h>

static void signal_handler(int sig) {
    printf("Signal received!\n");
}

int main(void) {

    if(fork() == 0) {
        sleep(2);
        kill(getppid() , SIGUSR1);
    }

    signal(SIGUSR1, signal_handler);
    sleep(10);

    return EXIT_SUCCESS;
}


(Замечания: кажется, использовать printf() внутри обработчика сигнала это плохая идея, но для простого примера пойдёт)

Попробуем собрать:

[c0der@rock ~/gcc-extensions]$ gcc unused.c -o unused -W -Wall
unused.c:8: предупреждение: unused parameter 'sig'


Ну, что я вам говорил? :)

Так вот, есть способ сказать компилятору, что конкретно этот аргумент нужен, что он даже если и выглядит как неиспользуемый, на самом деле необходим. Для этого вам нужно использовать специальное расширение gcc — указание аттрибута для переменной или функции.

Измените определение функции signal_handler() на следующее:

static void signal_handler(int sig __attribute__((unused))) {

И всё! Теперь нет никакого warning'а в данном конкретном случае.

Но это ещё не всё :) Есть ещё и...


Аттрибут format.



Часто в проектах программисты определяют свою printf-подобную функцию: она может выводить цветное сообщение, или же понимать дополнительные идентификаторы для пользовательского типа данных, или же после отображения сообщения завершать программу, да мало ли чего может придти на ум :)

Вот ещё одна бесполезная, но работающая, програмка: она выводит в цвете приветствие пользователю. Имя пользователя должно быть передано в качестве первого аргумента. (BTW, функция color_printf() позаимствована из реального проекта, в котором я сейчас копаюсь...)

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>

enum fg_colors {
    RED = 31, 
    GREEN,
    YELLOW,
    BLUE,
    VIOLET,
    AZURE
};

static void color_printf(enum fg_colors color, const char *format, ...) {
    printf("\033[%d;40m", color);

    va_list args;
    va_start (args, format);
    vprintf (format, args);
    va_end (args);

    puts("\033[0m");
}

int main(int argc, char *argv[]) {

    if (argc != 2) {
        fprintf(stderr, "Usage: %s name\n", argv[0]);
        return EXIT_FAILURE;
    }

    color_printf(GREEN, "Hello, %s!");

    return EXIT_SUCCESS;
}


Одна тонкость: в ней есть ошибка — мы вызываем color_printf() с недостаточным количеством аргументов, видать программист торопился и забыл один. Попробуем собрать:

[c0der@rock ~/gcc-extensions]$ gcc -W -Wall format.c -o format
[c0der@rock ~/gcc-extensions]$ ./format
Usage: ./format name
[c1der@rock ~/gcc-extensions]$ ./format Slava
Hello, €©™ї\pк·!


Видите, ни одного предупреждения от компилятора. В данном случае программа даже работает, хоть и вывела всякую случайную чушь из памяти, а ведь может и сегфолтнуться.

Если бы подобную ошибку вы допустили при использовании printf() или другой подобной стандартной функции, то gcc бы выдал предупреждение, но в случае использования самописной функции с неизвестным количеством аргументов он так не поступает. Впрочем, это можно исправить — надо просто ему об этом намекнуть ;)

Исправьте определение функции print_color() на следующее:


static void __attribute__((format(printf, 2, 3))) color_printf(enum fg_colors color, const char *format, ...) {


И попробуем скомпилировать ещё разочек:

[c0der@rock ~/gcc-extentions]$ gcc -W -Wall format.c -o format
format.c: В функции 'main'
format.c:32: предупреждение: недостаточно аргументов для указанного формата


Объясню лишь что значат аргументы у атрибута: printf значит, что наша функция подобна printf() (можно указать, что подобна scanf(), например), цифры означают какой из аргументов функции является строкой с форматом и когда начинаются переменные, которые необходимо будет подставить в строку формата.

Также есть несколько интересных атрибутов, вроде noreturn и const, но они более полезны компилятору при генерации кода, чем программисту, поэтому предлагаю изучить их самостоятельно, если кому интересно.

Updated(20070814): Ссылки по теме:


А также пример того, как обернуть директивы в макросы, чтобы не было проблем при сборке старой версией GCC или другим компилятором.

/* The attribute noreturn is not implemented in GCC versions earlier
 * than 2.5
 **/
#if defined(__GNUC__) && ___GNUC__ >= 2 && __GNUC_MINOR__ >= 5
    #define __noreturn  __attribute__((noreturn))
#else
    #define __noreturn
#endif


#ifdef __GNUC__
    #define __format(archetype, format_string, argument) \
        __attribute__((format(archetype, format_string, argument)))
#else
    #define __format(archetype, format_string, argument)
#endif