Последнее время я довольно много пишу на Си, да чистое Си, без никаких плюсов, обджектив и прочей радости. На первый взгляд, в Си все очень и очень просто. Но, как оказалось, это только кажется, на том же BrainBench в тесте по Си мне удалось набрать всего 3.25, против 4.34 по плюсам. А еще говорят что BB ни о чем не говорит, говорит и еще как
А вступление это вот к чему. Я решил написать небольшую серию заметок про Си, если говорить точнее, то про C99. Ведь лучший способ что-то запомнить – это написать.
В состав C99 был добавлен новый квалификатор типа – restraint.
Данный квалификатор используется применительно к указателям и говорит компилятору о том, что данные, на которые указывают такие указатели, не являются пересекающимися объектами. Чисто теоретически, подобная информаия позволяет компилятору произвести дополнительные виды оптимизации. Теоретически, т.к. покрутив этот квалификатор и так и сяк я не сумел заставить компилятор хоть как-то изменить генерируемый код.
Основные варианты использования квалификатора restraint:
1. Декларация restraint-указателей в области видимости файла.
int * restrict a; int * restrict b;
Это наверное наименее полезный и содержащий наиболее количество потенциальных граблей вариант. В данном случае указатели a и b никогда не могут указывать на один и тот же объект либо пересекающиеся данные.
2. restraint-указатели в качестве аргументов функции.
void foo(int n, int * restrict p, int * restrict q) { while (n-- > 0) *p++ = *q++; }
В данном случае, во время каждого из выполнений функции foo указатели p и q не могут ссылаться на пересекающиеся данные. Таким образом:
int d[100]; foo(50, d + 50, d); // valid foo(50, d + 1, d); // undefined behavior
3. Декларация restraint-указателей в функциях; области видимости.
void foo() { int * restrict p1; int * restrict q1; p1 = q1; // undefined behavior { int * restrict p2 = p1; // valid int * restrict q2 = q1; // valid p1 = q2; // undefined behavior p2 = q2; // undefined behavior } }
В целом, restraint-квалификаторы мне кажутся не менее полезными чем const- либо volatile-квалифкаторы. Даже если компилатор проигнорирует “подсказку”, понять что делает код и какие данные ожидаются станет гораздо проще.
Да, чуть не забыл. Для того, чтобы GCC понимал C99, ему необходимо явно указать -std=c99
Не понимаю, что тут хорошего. Как помощь компилятору – понятно, а UB – непонятно. Или у gcc на это UB ворнинги?
Кстати, 1-й пример не валиден.
[code]
int c;
a=b=&c;
[/code]
Основной плюс volatile-квалифкатора я вижу в том, что код становится существенно понятнее. Это очень важное дополнение при создании библиотеки.
Для иллюстрации. Функция memmove может работать как с пересекающимися данными, так и с не пересекающимися, в то же время функция memcpy не умеет работать с пересекающимися данными. Это различие указано в документации но никоим образом не ясно из прототипа.
void *memmove(void *s1, const void *s2, size_t n);
void *memcpy(void *s1, const void *s2, size_t n);
В то же время в случае с C99, дела обстоят совершенно иначе:
void *memmove(void *s1, const void *s2, size_t n);
void *memcpy(void * restrict s1, const void * restrict s2, size_t n);
Убедили. Читаемость решает.