понедельник, 23 августа 2010 г.

Расширение библиотеки GNU C для безопасного ввода строк.

    Не правильное применение функции scanf приводит к проблемам с безопасностью. Ниже приведён код, в котором произойдет переполнение буфера при введении 10 и более символов:  

    char str[10];
    scanf("%s", str);

    Чтобы избежать переполнения можно указать ширину поля:

    char str[10];
    scanf("%9s", str);

    В этом случае будет прочитано не более 9 символов, но если было введено больше символов, то в буфере stdin остаются лишние символы и перед следующим чтением буфер нужно очистить. Для избежания переполнения буфера, при использовании функции scanf, библиотека GNU C поддерживает не стандартное расширение, которое автоматически выделять достаточно память под вводимые данные. Для использования этого расширения нужно указать модификатор a как модификатор длинный %as:

    char *str;
    if ( scanf("%as", &str) == 1 )
    // указатель на char* указатель для адреса буфера
    {
        printf("%s\n", str);
        free(str);
    }

    При успешном выполнении функции scanf вызывающий код должен освободить выделенную память функцией free. Модификатор a не доступен если программа компилируется с флагом -std=c99 или -D_ISOC99_SOURCE. Начиная с версии 2.7 библиотека поддерживает модификатор m, который действует как a, но доступен при -std=c99.
    Вместо функции scanf рекомендуется пользоваться другим расширением библиотека GNU C, функцией getline, которая позволяет надёжно читать строку произвольной длинны и самостоятельно выделяет необходимый объёма памяти:

    ssize_t getline(char **lineptr, size_t *n, FILE *stream);
    lineptr — указатель на char* указатель для адреса динамически выделенного буфера.
                    Если инициализирован NULL, getline сам выделит память иначе должен
                    указывать на буфер выделенный с помощью функции malloc.
    n — указатель на размер буфера. Если буфер выделен с помощью malloc, *n должно
            содержать размер буфера, иначе 0.
    stream — поток, откуда читаются данные.


    Пример использования:

    #define _GNU_SOURCE
    #include <stdio.h>
    #include <stdlib.h>
    char* pUserName = NULL;
    size_t bytes_read, nbytes = 0;

    bytes_read = getline(&pUserName, &nbytes, stdin);
    if ( bytes_read != -1 )
    {
        printf("%s", pUserName);
    }

    if ( pUserName )
        free(pUserName);

    При достижении конца файла или ошибке функция возвращает -1. В случае успешного завершения функция возвращает число прочитанных символов (включая символ перевода строки, но не, включая пустой символ завершения) и может обновлять значения *lineptr и *n, если буфер изменялся.

вторник, 10 августа 2010 г.

Библиотека GNU Nana. Часть 1.

    GNU Nana это C/C++ библиотека, которая реализует проверку утверждений, ведения лога (можно выводить сообщения в файл, на терминал, другой процесс и буфер в памяти), измерения времени выполнения участка кода, трассировку выполнения программы и генерацию сокращённой формы программы в HTML файла (в нем будут содержаться заголовки функций и их предусловия и постусловия). Реализация утверждений в GNU Nana имеет минимальные издержки времени выполнения и размера добавочного кода, кроме того утверждения можно включать и выключать как при компиляции, так и в процессе выполнения программы.

    Проверка утверждений в стиле Си.
   Для использования этого интерфейса в программе, нужно подключить заголовочный файл nano.h. Ниже приведены основные макросы (в библиотеке так-же есть макросы которые кроме проверки условия позволяют задавать поведение при возникновении ошибочной ситуации.)
    Макрос void I(bool exprn). Если выражение exprn true, то программа является корректной.
    Макрос void N(bool exprn). Если выражение exprn false, то программа является корректной.

#include <nano.h>
void foo(int d)
{
    I(d != 0);
    x = y / d;
    ...
}

    В библиотеке реализованы так-же макросы позволяющие сохранить предыдущие значение переменной:
    Макрос void ID (Text decln).
    Макрос void IS (Text assignment).

#include <nano.h>
void foo(int* v, char z)
{
    ID(int* s = v);
    ID(char t);
    IS(t = z);
    ...
    I(s == v);
    I(t == z);
}

    Проверка утверждений в отладчике GDB.
    Утверждения проверяются только, если программа откомпилирована с флагом -g и для GDB создан сценарий.

    Макрос void DI(bool exprn) аналогично I().
    Макрос void DS(e) аналогично IS().

#include <stdio.h>
#include <nana.h>

int main(int ac, char** av)
{
    int a = 10;
    DS($s = a);
    printf("Hello! %d\n", a);
    DS(var a = 5);
    printf("Hello! %d\n", a);
    DI($s == a);
    return 0;
}

Компиляция и результат работы:
gcc -g nt.c -o nt -lnana
nana nt.c > nt.gdb
gdb nt
(gdb) source
nt.gdb
Hello! 10
Hello! 5
"DI(""$s ==a"")" has failed at f:l with
#0 main (ac=1,av=0xbffff4f4) at nt.c:12

Сайт проекта
Контрактное программирование