Skip to content

acidpuzzle/KNF-RU

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 

Repository files navigation

Kernel source file style guide (KNF)

Этот файл определяет предпочтительный стиль исходных файлов ядра в дереве исходных текстов OpenBSD. Он также является руководством по предпочтительному стилю кода пользовательского пространства. Этим рекомендациям следует следовать для всего нового кода. В общем случае, код можно считать "новым", если он составляет около 50% или более от общего объема файла(ов). Этого достаточно, чтобы отказаться от прецедентов в существующем коде и использовать текущее руководство по стилю.

/*
 * Style guide for the OpenBSD KNF (Kernel Normal Form).
 */
/*
 * ОЧЕНЬ важные однострочные комментарии выглядят следующим образом.
 */
/* Большинство однострочных комментариев выглядят так. */
/*
 * Многострочные комментарии выглядят следующим образом.  Сделайте их настоящими предложениями.
 * Заполните их так, чтобы они выглядели как настоящие параграфы.
 */

На первом месте стоят включаемые файлы ядра (т.е. <sys/*.h>); обычно требуется <sys/types.h> ИЛИ <sys/param.h>, но не оба! <sys/types.h> включает <sys/cdefs.h>, и от него можно зависеть.

#include <sys/types.h>   /* Не локальное включение, угловые скобки. */

Если это сетевая программа, поместите сетевые включаемые файлы рядом.

#include <net/if.h>
#include <net/if_dl.h>
#include <net/route.h>
#include <netinet/in.h>

Затем идет пустая строка, за которой следуют файлы /usr/include. Файлы /usr/include, по большей части, должны быть отсортированы.

Глобальные имена путей определяются в файле /usr/include/paths.h. Локальные для программы имена путей находятся в файле pathnames.h в локальном каталоге.

#include <paths.h>

Затем идет пустая строка и включаемые пользователем файлы.

#include "pathnames.h"   /* Локальные включения заключаются в двойные кавычки. */

Все нестатические функции где-то прототипируются.

Прототипы функций для частных функций (т.е. функций, не используемых в других местах) размещаются в верхней части первого исходного модуля. В ядре частные функции не требуют прототипа, если они определены до их использования. В пространстве пользователя функции, локальные для одного исходного модуля, должны быть объявлены как "статические". В ядре этого делать не следует, так как это делает невозможным использование отладчика ядра.

Функции, используемые из других файлов, прототипируются в соответствующем включаемом файле.

Функции, которые используются локально более чем в одном модуле, выносятся в отдельный заголовочный файл, например, extern.h.

Прототипы не должны иметь имен переменных, связанных с типами; т.е,

void function(int);

НЕТ:

void function(int a);

Прототипы могут иметь дополнительный пробел после табуляции для того, чтобы имена функций были выстроены в ряд:

static char	*function(int, const char *);
static void	 usage(void);

Между именем функции и списком аргументов не должно быть пробела.

Используйте __dead из <sys/cdefs.h> для функций, которые не возвращают, т.е,

__dead void    abort(void);

В заголовочных файлах прототипы функций следует помещать в пары соответствия __BEGIN_DECLS / __END_DECLS. Это делает заголовочный файл пригодным для использования из языка Си++.

Макросы выделяются заглавными буквами и заключаются в круглые скобки, при этом следует избегать побочных эффектов. Если макрос представляет собой встроенное расширение функции, то функция определяется в нижнем регистре, а макрос имеет то же имя в верхнем регистре. Если макрос занимает более одной строки, используйте скобки. Обратные косые черты следует выравнивать вправо, так как полученное определение легче читается. Если макрос содержит составной оператор, заключите его в цикл "do", чтобы его можно было безопасно использовать в операторах "if". Любая точка с запятой, завершающая оператор, должна быть предоставлена вызовом макроса, а не самим макросом, чтобы облегчить разбор для красивых принтеров и редакторов.

#define MACRO(x, y) do {                          \
	variable = (x) + (y);                        \
	(y) += 2;                                    \
} while (0)

Если в макросе с аргументами объявляются локальные переменные, то эти переменные должны иметь идентификаторы, начинающиеся с двух знаков подчеркивания. Это требуется для макросов, реализующих интерфейсы C и POSIX, и рекомендуется для всех макросов для согласованности.

Значения перечислений приводятся в верхнем регистре.

enum enumtype { ONE, TWO } et;

При определении беззнаковых целых чисел следует использовать "unsigned int", а не просто "unsigned"; последнее в прошлом вызывало путаницу.

При объявлении переменных в структурах следует объявлять их в порядке использования, затем по размеру (от наибольшего к наименьшему), затем в алфавитном порядке. Первая категория обычно не применяется, но бывают и исключения. Каждому из них отводится отдельная строка. Поставьте табуляцию после первого слова, т.е. используйте 'int^Ix;' и 'struct^Ifoo *x;'.

Основные структуры должны быть объявлены в верхней части файла, в котором они используются, или в отдельных заголовочных файлах, если они используются в нескольких исходных файлах. Использование структур должно осуществляться отдельными объявлениями и должно быть "extern", если они объявлены в заголовочном файле.

struct foo {
	struct	foo *next;		/* List of active foo */
	struct	mumble amumble;	/* Comment for mumble */
	int		bar;
};
struct foo *foohead;			/* Head of global foo list */

По возможности используйте макросы queue(3), а не составляйте собственные списки. Таким образом, предыдущий пример лучше было бы написать так:

#include <sys/queue.h>
struct    foo {
	LIST_ENTRY(foo)	link;			/* Queue macro glue for foo lists */
	struct			mumble amumble;	/* Comment for mumble */
	int				bar;
};
LIST_HEAD(, foo) foohead;				/* Head of global foo list */

Избегайте использования типизаций для типов структур. Это делает невозможным непрозрачное использование указателей на такую структуру, что возможно и выгодно при использовании обычного тега struct. Если по правилам необходимо использовать typedef, то его имя должно соответствовать тегу struct. Избегайте типизаций, заканчивающихся на "_t", за исключением случаев, оговоренных в стандарте C или POSIX.

/*
 * Все основные подпрограммы должны иметь комментарий, кратко описывающий их
 * работу.  Комментарий перед "главной" процедурой должен описывать, что делает
 * программа.
 */
int
main(int argc, char *argv[])
{
	int aflag, bflag, ch, num;
	const char *errstr;

Для согласованности следует использовать getopt(3) для разбора опций. Параметры должны быть отсортированы в вызове getopt(3) и операторе switch, если только они не являются частью каскада switch. Элементы в операторе switch, которые каскадируются, должны иметь комментарий FALLTHROUGH. Числовые аргументы должны быть проверены на точность.

while ((ch = getopt(argc, argv, "abn:")) != -1) {
	switch (ch) {		/* Indent the switch. */
	case 'a':			/* Don't indent the case. */
		aflag = 1;
		/* FALLTHROUGH */
	case 'b':
		bflag = 1;
		break;
	case 'n':
		num = strtonum(optarg, 0, INT_MAX, &errstr);
		if (errstr) {
			warnx("number is %s: %s", errstr, optarg);
			usage();
		}
		break;
	default:
		usage();
	}
}
argc -= optind;
argv += optind;

Используйте пробел после ключевых слов (if, while, for, return, switch). Для управляющих операторов с нулевым или только одним оператором скобки не используются, если только этот оператор не занимает более одной строки, в этом случае они разрешены.

for (p = buf; *p != '\0'; ++p)
	continue;
for (;;)
	stmt;
for (;;) {
	z = a + really + long + statement + that + needs +
		two + lines + gets + indented + four + spaces +
		on + the + second + and + subsequent + lines;
}
for (;;) {
	if (cond)
		stmt;
}

Части цикла for могут оставаться пустыми.

for (; cnt < 15; cnt++) {
		stmt1;
		stmt2;
}

Отступ представляет собой табуляцию из 8 символов. Отступы второго уровня — четыре пробела. Весь код должен умещаться в 80 столбцов.

while (cnt < 20)
		z = a + really + long + statement + that + needs +
			two + lines + gets + indented + four + spaces +
			on + the + second + and + subsequent + lines;

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

Закрывающие и открывающие скобки располагаются на одной строке с else. Необязательные скобки можно не ставить, если они не вызывают предупреждения компилятора.

if (test)
		stmt;
else if (bar) {
		stmt;
		stmt;
} else
		stmt;

Не используйте пробелы после имен функций. После запятых ставится пробел. Не используйте пробелы после символов '(' или '[' или предшествующих им ']' или ')'.

if ((error = function(a1, a2)))
	exit(error);

Унарные операторы не требуют пробелов, а бинарные - требуют. Не используйте круглые скобки, если они не требуются для определения старшинства, если высказывание без них не является запутанным или если компилятор не выдает предупреждение. Помните, что другие люди могут запутаться легче, чем вы. Понимаете ли ВЫ следующее?

a = b->c[0] + ~d == (e || f) || g && h ? i : j >> 1;
k = !(l & FLAGS);

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

/*
 * Избегайте очевидных комментариев, таких как
 * "Exit 0 on success."
 */
exit(0);

Тип функции должен располагаться в строке перед самой функцией.

static char *
function(int a1, int a2, float fl, int a4)
{

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

Будьте осторожны, чтобы не запутать код, инициализируя переменные в объявлениях. Используйте эту возможность только обдуманно. НЕ используйте вызовы функций в инициализаторах!

struct foo one, *two;
double three;
int *four, five;
char *six, seven, eight, nine, ten, eleven, twelve;

four = myfunction();

Не объявляйте функции внутри других функций.

После кастов и вызовов sizeof() пробел не ставится. Заметим, что indent (1) не понимает этого правила.

Использование спецификатора "register" в новом коде не рекомендуется. Оптимизирующие компиляторы, такие как gcc, обычно лучше справляются с выбором переменных, которые следует поместить в регистры для повышения производительности кода. Исключение составляют функции, содержащие ассемблерный код, где спецификатор "register" необходим для правильной генерации кода при отсутствии оптимизации компилятора.

При использовании в программе функций longjmp() или vfork() следует использовать флаг -W или -Wall, чтобы убедиться, что компилятор не выдает предупреждений типа

warning: variable `foo' might be clobbered by `longjmp' or `vfork'.

При появлении предупреждений такого типа необходимо применить к переменной квалификатор типа volatile. Невыполнение этого требования может привести к некорректной генерации кода при включенной оптимизации. Обратите внимание, что для указателей местоположение volatile определяет, к чему относится квалификатор типа - к указателю или к тому, на что он указывает. Волатильный указатель объявляется с volatile справа от "*". Пример:

char *volatile foo;

говорит о том, что "foo" является volatile, а "*foo" - нет. Чтобы сделать "*foo" volatile, используйте синтаксис

volatile char *foo;

Если и указатель, и объект, на который он указывает, изменчивы, используйте

volatile char *volatile foo;

const также является классификатором типа, и к нему применимы те же правила. Описание аппаратного регистра, доступного только для чтения, может выглядеть следующим образом:

const volatile char *reg;

Глобальные флаги, устанавливаемые в обработчиках сигналов, по возможности должны иметь тип volatile sig_atomic_t. Это гарантирует, что доступ к переменной может быть осуществлен как к атомарной сущности, даже если сигнал уже подан. Глобальным переменным других типов (например, структурам) не гарантируется постоянство значений при обращении к ним через обработчик сигнала.

NULL является предпочтительной константой нулевого указателя. Используйте NULL вместо (type *)0 или (type *)NULL во всех случаях, кроме аргументов вариативных функций, где компилятор не знает тип.

Не используйте '!' для тестов, если они не являются булевыми, т.е. используйте

if (*p == '\0')

НЕТ:

if (!*p)

Маршруты, возвращающие void *, не должны приводить возвращаемые значения к какому-либо типу указателя

Используйте семейство функций err(3) и warn(3). Не создавайте собственных!

if ((four = malloc(sizeof(struct foo))) == NULL)
	err(1, NULL);
if ((six = (int *)overflow()) == NULL)
	errx(1, "Number overflowed.");
return eight;

Всегда используйте определения функций в формате ANSI. Длинные списки параметров заворачиваются с обычным отступом в четыре пробела.

Переменное число аргументов должно выглядеть следующим образом:

#include <stdarg.h>

void
vaf(const char *fmt, ...)
{
	va_list ap;
	va_start(ap, fmt);

	STUFF;

	va_end(ap);

	/* No return needed for void functions. */
}

static void
usage(void)
{

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

Перекладина ('|') разделяет опции/аргументы "или-или", а несколько опций/аргументов, заданных вместе, помещаются в одну группу скобок.

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

"usage: f [-12aDde] [-b b_arg] [-m m_arg] req1 req2 [opt1 [opt2]]\n"
"usage: f [-a | -b] [-c [-de] [-n number]]\n"

Функцию getprogname(3) можно использовать вместо жесткого кодирования имени программы.

fprintf(stderr, "usage: %s [-ab]\n", getprogname());
exit(1);

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

По возможности код должен быть прогнан через программу проверки кода (например, gcc -Wall -W -Wpointer-arith -Wbad-function-cast ... или splint из дерева портов) и выдавать минимум предупреждений. Поскольку lint был удален, единственным комментарием в стиле lint, который следует использовать, является FALLTHROUGH, поскольку он полезен для человека. Другие комментарии в стиле lint, такие как ARGSUSED, LINTED и NOTREACHED, могут быть удалены.

About

Kernel source file style guide (KNF)

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published