[cdev] Задача_6. гр 135

Grigoriy A. Sitkarev sitkarev на komitex.ru
Пн Сен 20 08:21:31 MSK 2010


Как-то незаконченно получилось. Я продолжу.

Так как мы уже макросы написали, мы потом в public функциях проверки 
будем делать так:

struct prioq_head *
prioq_get(struct prioq *queue)
{
	struct prioq_head *head;
	struct user_data *ptr;
	unsigned int used;

	return_val_if_fail(queue != NULL, NULL);

	...

Это в том случае если функция что-то возвращает. Если же ничего не 
возвращает, то используется макрос return_if_fail.

void
prioq_free(struct prioq **queue)
{
	return_if_fail(queue != NULL);
	return_if_fail(*queue != NULL);


	...

Для проверки состояний, которые однозначно ошибочны внутри функций, 
служит макрос assert(3). Но мы написали вместо него свой. Он посылает 
процессу сигнал SIGABRT через abort(3), таким образом если программа 
была запущена под управлением отладчика, она остановится в том месте 
проверка assert-ом не прошла.

Есть ещё один нюанс, который видимо пока не совсем ясен.

Например, в функции prioq_get() у тебя идёт проверка аргументов таким 
образом:

	if (queue == NULL || queue->used == 0) {
		fprintf(stderr,  "prioq_get: Can't extract "
			"head from queue\n");
		return NULL;
	}

С проверкой указателя queue всё понятно, и это правильно, надо его 
проверить, потому что программист-пользователь нашей библиотеки мог 
допустить ошибку и передать туда NULL или же передал туда указатель, 
который был обнулён уже (например prioq_free()), потому что пять же 
ошибся в программе и использует очередь, которой уже не существует.

Но, что касается следующей проверки, счётчика элементов в очереди, здесь 
  формально ошибки нет, но есть ошибка функциональная. Дело в том что 
состояние, при котором в очереди нет элементов, не является ошибкой. 
Если кто-то попытался извлечь из пустой очереди элемент, то ошибки нет, 
просто мы возвращаем NULL, пользователь проверяет всегда возвращённое 
значение, и если элемента не было, то ничего с ним не делает. У тебя же 
получается что состояние (queue->used == 0) обнаруживается как ошибка 
программиста, потому что на стандартный вывод ошибок ты печатаешь 
сообщение о том что невозможно извлечь элемент из очереди.

Правильный вариант был бы такой:

	if (queue == NULL) {
		fprintf(stderr,  "prioq_get: Can't extract "
			"head from queue\n");
		return NULL;
	}

	if (queue->used == 0)
		return NULL;

Ну или с использованием макроса:

	return_val_if_fail(queue != NULL, NULL);

	if (queue->used == 0)
		return NULL;

Конечно же нельзя делать проверку на (queue->used == 0) до того, как мы 
проверим сам указатель queue. Потому что queue может быть NULL а мы его 
в проверке станем разыменовывать ``(->used'', т.е. ходить по смещению от 
адреса queue (от нуля) к полю used, что непременно приведёт к ошибке 
сегментации и ядро доставит процессу сигнал SIGSEGV.

Можешь проверить что будет в таком случае.

struct prioq *q;

q = ((struct prioq *)(NULL))->used;

А NULL это константа, определённая где-то в заголовках примерно так:

#define NULL	((void *)(0))

Между прочим, аналогичным способом можно и вычислять смещение любого 
поля в структуре, в байтах. Если я возьму адрес 0 и разыменую его как 
указатель на структуру, а потом возьму его адрес... То как раз получится 
смещение.

unsigned long offset;

offset = &((struct prioq *)(NULL))->used;

В offset мы получим смещение в байтах адреса где хранится поле used 
относительно начального адреса структуры prioq.

В Си и системном программировании часто случается что нам нужно имея 
указатель на поле структуры получить указатель на её начало. Тогда 
удобно написать такой макрос:

#define CONTAINER_OF(ptr, type, field) \
	((type *)((unsigned long)(ptr) - (unsigned long)(&((type *)(0))->field)))

Тогда, например, имея указатель (адрес) любого поля внутри структуры мы 
бы могли получить указатель на объемлющую структуру. Я вложил файл 
containter.c там это продемонстрировано на простом примере. Таким 
образом можно организовывать некое подобие наследования. Из практических 
соображений удобно писать функции для работы с абстрактными типами 
данных, не привязанных к какой-то объемлющей структуре пользователя. 
Думаю что пример в containter.c всё проясняет.

--
Г.А.

> Тебе нужно подумать и где-то исправить проверки входных аргументов. 
> Внесём некоторую ясность сюда. Зачем мы вообще проверяем аргументы? 
> Нужно ли это делать всегда во всех функциях, и если не во всех, то тогда 
> в каких?
> 
> Здесь можно руководствоваться таким практическим правилом. Проверку 
> аргументов на их валидность (нулевые указатели, выход за границы 
> массивов и т.д.) следует делать в функциях, которые относятся к public 
> интерфейсу библиотеки, т.е. во всех функциях, которые могут быть 
> напрямую вызваны пользователем. Это значит что, как правило, во 
> внутренних функциях такие проверки делать уже не нужно т.к. мы их уже 
> сделали раньше, в тот момент когда пользовател вызывал public функцию.
> 
> Как мы делаем эти проверки?


----------- следущая часть -----------
A non-text attachment was scrubbed...
Name: container.c
Type: text/x-csrc
Size: 1205 bytes
Desc: отсутствует
URL: <http://amplab.syktsu.ru/pipermail/cdev/attachments/20100920/c79a28bd/attachment.c>


Подробная информация о списке рассылки cdev