Комментарии в коде C и C#: синтаксис, документация и правила

Комментарии в коде C и C#: синтаксис, документация и правила Полезное

Комментарий — это текст в исходном коде, который компилятор не исполняет: в C на ранней фазе трансляции каждый комментарий заменяется одним пробелом, в C# лексер пропускает его как незначащий фрагмент. Комментарии пишут для людей, которые будут читать код.

C и C# — разные языки, но C# унаследовал синтаксис комментариев от семейства C, поэтому // и /* */ в них выглядят одинаково. Различия начинаются в документирующих комментариях: в C# есть встроенный формат XML-документации ///, в C стандартного формата нет, и обычно используют Doxygen.

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

Три вида комментариев: мини-словарь

Термин Для кого Пример Что с ним делает инструмент
Обычный комментарий для читателя кода // повтор при таймауте компилятор отбрасывает
Документирующий комментарий для пользователя функции или API /// <summary>...</summary> IDE показывает в подсказках, генератор собирает документацию
Закомментированный код временно отключенный фрагмент // printf("debug"); компилятор отбрасывает, но это не пояснение, а мусор в истории

Синтаксис комментариев в C

В C два вида комментариев:

  • блочный /* ... */ — начинается с /*, заканчивается первым встреченным */, может занимать несколько строк; есть в языке с первого стандарта C89;
  • однострочный // ... — действует до конца строки; в стандарт C вошел в C99 (многие компиляторы понимали его и раньше как расширение).

Минимальный полный пример, который компилируется и запускается:

#include <stdio.h>

/* Площадь прямоугольника.
   Размеры в сантиметрах, результат в квадратных сантиметрах. */
static int area(int width, int height)
{
    return width * height;  // переполнение int не проверяем: учебный пример
}

int main(void)
{
    int w = 12, h = 5;
    // Выводим результат одной строкой
    printf("area = %d\n", area(w, h));
    printf("// это не комментарий, а часть строки\n");
    return 0;
}

Программа печатает две строки:

area = 60
// это не комментарий, а часть строки

Что здесь видно:

  • блочный комментарий над функцией описывает контракт: единицы измерения, которых нет в коде;
  • комментарий справа от return называет границу примера, а не пересказывает умножение;
  • // внутри строкового литерала — это просто символы строки. Комментарием он становится только вне литералов.

Синтаксис комментариев в C

В C# те же // и /* */ плюс документирующий комментарий /// (реже встречается блочная форма /** ... */).

using System;

public static class Geometry
{
    /// <summary>
    /// Считает площадь прямоугольника.
    /// </summary>
    /// <param name="width">Ширина в сантиметрах, не меньше нуля.</param>
    /// <param name="height">Высота в сантиметрах, не меньше нуля.</param>
    /// <returns>Площадь в квадратных сантиметрах.</returns>
    /// <exception cref="ArgumentOutOfRangeException">Если размер отрицательный.</exception>
    public static int Area(int width, int height)
    {
        ArgumentOutOfRangeException.ThrowIfNegative(width);
        ArgumentOutOfRangeException.ThrowIfNegative(height);
        return width * height; // checked-контекст не включаем: учебный пример
    }

    public static void Main()
    {
        /* Блочный комментарий работает так же, как в C */
        Console.WriteLine($"area = {Area(12, 5)}"); // выведет: area = 60
    }
}

При запуске в консоли появится area = 60. Главное здесь — комментарий ///: в Visual Studio, Rider и VS Code с расширением C# текст из <summary> и <param> всплывает в подсказке, когда вы вызываете Geometry.Area.

Основные XML-теги документации:

Тег Что описывает
<summary> краткое назначение типа или метода
<param name="..."> один параметр
<returns> возвращаемое значение
<exception cref="..."> исключение и условие, при котором оно бросается
<remarks> подробности, которые не влезли в summary
<example>, <code> пример использования
<inheritdoc/> взять документацию у базового члена или интерфейса

Чтобы компилятор собрал XML-файл документации рядом со сборкой, в .csproj включают свойство:

<PropertyGroup>
  <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

После этого компилятор начинает предупреждать о публичных членах без документации (предупреждение CS1591).

Документирующие комментарии в C: Doxygen

У C нет встроенного формата документации, де-факто стандарт — Doxygen. Он читает комментарии вида /** ... */ или /// с командами @brief, @param, @return:

/**
 * @brief  Считает площадь прямоугольника.
 * @param  width   ширина в сантиметрах
 * @param  height  высота в сантиметрах
 * @return площадь в квадратных сантиметрах
 */
int area(int width, int height);

Для компилятора это обычный блочный комментарий: без запуска Doxygen он ничего не меняет в программе. Комментарий к функции обычно ставят в заголовочный файл .h, потому что именно его читает пользователь функции.

Типичные ошибки с комментариями в C

Ошибка 1: вложенный блочный комментарий

Блочные комментарии в C (и в C#) не вкладываются: комментарий заканчивается на первом */. Если закомментировать блоком фрагмент, где уже есть /* */, хвост окажется снаружи.

    /* старый блок /* пояснение */ x = 0; */

Фактический результат (Apple clang 21; текст и порядок сообщений в GCC и MSVC другие):

warning: '/*' within block comment [-Wcomment]
error: use of undeclared identifier 'x'
error: expected expression

Исправление — отключать большие куски кода условной компиляцией, а не комментарием. Директивы #if 0 … #endif вкладываются и не конфликтуют с /* */ внутри:

#include <stdio.h>

int main(void)
{
#if 0
    /* старая версия расчета */
    printf("old\n");
#endif
    printf("new\n");
    return 0;
}

Выведется только new. В долгосрочной перспективе отключенный код лучше удалить: старая версия останется в истории Git.

Ошибка 2: обратный слеш в конце однострочного комментария

Обратный слеш в самом конце строки склеивает ее со следующей еще до того, как компилятор ищет комментарии. Если //-комментарий заканчивается на \, следующая строка тоже становится комментарием.

#include <stdio.h>

int main(void)
{
    int limit = 10;
    // временный каталог: C:\temp\
    limit = 99;
    printf("limit = %d\n", limit);
    return 0;
}

Фактический результат: программа печатает limit = 10, присваивание limit = 99 молча пропало. Clang с -Wall предупреждает multi-line // comment [-Wcomment], без флагов предупреждений ошибка может пройти незаметно. Исправление — не заканчивать комментарий обратным слешем: написать C:\temp без завершающего слеша или взять путь в кавычки.

Ошибка 3: комментарий, который врет

Компилятор не сверяет комментарий с кодом. Если логику поменяли, а комментарий нет, он начинает вводить в заблуждение:

/* Возвращает площадь в квадратных метрах */
int area(int width, int height);   /* на деле - сантиметры */

Исправление — править комментарий в том же коммите, что и код, а единицы и ограничения по возможности выражать в именах: area_cm2(int width_cm, int height_cm).

Как писать полезные комментарии

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

Ситуация Вместо комментария Когда комментарий нужен
непонятно, что хранит переменная переименовать: t -> timeout_ms единица или диапазон не выражаются в имени
длинный кусок логики вынести в функцию с говорящим именем неочевидная причина решения
обход бага библиотеки или ОС — всегда: что за баг, где описан, когда можно убрать
формула или алгоритм — ссылка на источник и условия применимости
публичный API — документирующий комментарий: параметры, результат, ошибки

Сравните два комментария к одной строке:

i++;  /* увеличиваем i на 1 */
i++;  /* пропускаем заголовок CSV: первая строка файла - имена колонок */

Первый повторяет код и ничего не добавляет. Второй объясняет причину, которую из i++ не восстановить.

Короткий чек-лист перед коммитом:

  1. Комментарий отвечает на вопрос «почему», а не пересказывает «что».
  2. Он описывает текущее поведение, а не историю правок (история — в сообщениях коммитов).
  3. Публичные функции документированы: параметры, возвращаемое значение, ошибки.
  4. В коде не осталось закомментированных блоков без причины.
  5. Однострочный комментарий не заканчивается обратным слешем.

Выводы

  • В C есть /* */ (с C89) и // (с C99); в C# те же два вида плюс XML-документация ///.
  • Блочные комментарии не вкладываются ни в C, ни в C#; для отключения кода в C надежнее #if 0 … #endif, а лучше удаление и история Git.
  • // внутри строкового литерала не комментарий, а обратный слеш в конце //-строки превращает в комментарий и следующую строку.
  • Документирующие комментарии читают инструменты: в C# — компилятор и IDE (XML-файл, CS1591), в C — Doxygen.
  • Полезный комментарий объясняет причину, единицы, ограничения и обходы багов; то, что можно выразить именем, лучше выразить именем.

Где применяется / связь с практикой

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

Освойте тему на практике

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

FAQ

Влияют ли комментарии на скорость или размер программы?
Нет. Комментарии удаляются на этапе трансляции и в исполняемый файл не попадают. XML-документация C# сохраняется отдельным файлом рядом со сборкой, а не внутри нее.

Можно ли поставить комментарий в середину строки кода?
Да, блочный: int x = /* начальное */ 0; корректно в C и C#. Однострочный // так не получится, он отключает все до конца строки.

Как быстро закомментировать несколько строк в редакторе?
В VS Code — Ctrl+/ (на macOS Cmd+/), в Visual Studio — Ctrl+K, Ctrl+C, а раскомментировать — Ctrl+K, Ctrl+U. Сочетания зависят от раскладки и настроек редактора.

OTUS Журнал