Write or review Doxygen documentation comments for C code — especially structs and their members, plus enums, unions, typedefs, and functions. Use when adding/reviewing `/** */`, `/*! */`, or trailing `/**< */` doc comments in C, documenting binary-format/reverse-engineered structs with per-field offset notes, setting up a Doxyfile for a C-only project, or when the user says "document this struct/function/enum with Doxygen".
Installation
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Write or review Doxygen documentation comments for C code — especially structs and their members, plus enums, unions, typedefs, and functions. Use when adding/reviewing `/** */`, `/*! */`, or trailing `/**< */` doc comments in C, documenting binary-format/reverse-engineered structs with per-field offset notes, setting up a Doxyfile for a C-only project, or when the user says "document this struct/function/enum with Doxygen".
Document C code with Doxygen
Actionable rules and copy-pasteable patterns for documenting C (not C++) with Doxygen.
Prefer @ command prefixes (Javadoc style) over \ — both work; be consistent.
Golden rules
Every file that declares globals/typedefs/structs needs a @file block, or
Doxygen documents nothing file-level from it. This is the #1 gotcha.
Put the doc comment adjacent to the item (directly before, or after with
/**< */). Adjacency means Doxygen auto-associates it — no @struct/@enum/@var
needed. You only need those structural commands when the comment is not adjacent.
Brief vs detailed: with JAVADOC_AUTOBRIEF = YES, the first sentence (up to the
first ., ?, or ! + space) is the brief; a blank line starts the detail. Without
it, use explicit @brief / @details. Pick one convention project-wide.
Use trailing /**< */ for one-line-per-field structs/enums; use a preceding /** */
block when a field needs multiple lines.
Where the function block lives: public functions are documented in the .H at the
declaration; private (static) functions in the .C at the definition. One block per
function — never duplicate the same block in both files.
Every parameter gets a @param line — no exceptions. Self-evident names are not
exempt; the line then carries the contract instead (valid range, units, ownership,
whether it is bounds-checked). Keep it to the caller-facing WHAT — a @param that
merely restates the name means the contract is still missing, so find it (range?
nullability? who frees it?) and write that.
HARD RULE — every reference to a symbol is an @ref. No bare names, no autolinks, no
line pins. This codebase is under heavy refactoring: names get renamed, files get
split, lines shift. Only @ref / \ref is verified by Doxygen — an unresolved one
warns and fails the doc-lint. Autolinks (fn(), ::Type, Struct::member) and
@see/@sa render as links only while they resolve and rot silently when they
break, so they are not acceptable for naming a symbol. The rule, no exceptions:
Naming a function, type, member, macro, global, enum value, or file → @ref <name>
(function form: @ref foo, not @ref foo() — the parens aren't needed and @ref
renders the name without them anyway).
Target not yet visible to Doxygen (its header still lacks @file, or it isn't
documented) → the inert placeholder @c <name> (TBD-ref: <why>), literally the
token TBD-ref. Use parentheses, not a /* */ comment — a */ inside a /** */
doc block closes it early. Never #tbd — # is a live link operator and warns.
grep -rn 'TBD-ref' finds every one to upgrade to @ref once the target is documented.
Forbidden always: bare symbol names as prose, FILE.C:NNN pins, "see line 249".
Sequencing (so the gate doesn't cry wolf): @ref only resolves to a documented target,
and file-scope symbols need @file — so land @file/doc coverage before turning on
WARN_AS_ERROR. See Cross-referencing.
Wrap comment text at 80 columns. Every line of a doc block — @file, a
preceding /** */, or a trailing /**< */ — must fit within 80 columns; break the
prose onto a continuation line before it would cross column 80. The formatter will
not do this for you: the pinned clang-format style sets ReflowComments: false
(it must — auto-reflow mangles @ref/@param tags and byte-offset tables), so
wrapping is a hand discipline. When a trailing /**< */ would overflow, convert the
field to a preceding /** */ block (Rule 4) and wrap inside it. Never split an
@ref <name> or @p name across a line break — keep each reference token whole.
Comment block styles Doxygen recognizes
/** Javadoc block. *//*! Qt block. *//// C++ line comment/** /*! //! C++ line (Qt flavor)
* multi-line * multi-line
*/ */
Member post-comment — the critical one for struct fields (note the <):
int width; /**< trailing block: documents the PRECEDING member */int height; //!< trailing line comment, same effect
/**< */ and //!< can only document members and parameters — never a file, struct,
enum, union, or group as a whole.
1. File header (@file)
/**
* @file czone.h
* @brief Combat-zone actor pool and grid geometry.
* @details Structures and helpers for the 50x50 tile combat grid. All offsets
* in this file are relative to the on-disk TEMP.GAM save image.
* @author <name or reverse-eng credit>
* @date 1993
*/
Bare minimum if you just need file-level symbols to appear: /** @file */.
2. Struct with every member documented (trailing /**< */ style)
Use this compact style when each field fits on one line.
/**
* @brief One combatant on the tactical grid.
*
* Spawned from the zone actor pool; lives for the duration of one encounter.
*/typedefstructCombatActor {unsignedshort id; /**< Stable actor id; 0xFFFF = empty slot. */unsignedchar kind; /**< Dispatch tag: 1=solid, 2=blocker, 3=trigger. */unsignedchar flags; /**< Bitfield, see @ref CombatActorFlags. */short tileX; /**< Grid column, 0..49. */short tileY; /**< Grid row, 0..49. */short hp; /**< Current hit points; <=0 means dead. */
} CombatActor;
3. Struct member documented with a preceding block
Use a preceding /** */ when a field needs a longer explanation.
structPagedRecord {/**
* @brief Far pointer to the decompressed page payload.
*
* Owned by the record store; do not free directly — call
* @ref pagedrec_release so the LRU cache can reclaim the slot.
*/unsignedchar far *data;
unsignedshort pageId; /**< Source page index within the .DAT container. */
};
Mixing is fine and encouraged: trailing /**< */ for simple fields, a preceding
block for the one field that needs prose.
The primary use case here. Note offset, size, and meaning in each member comment so
the C stays truthful to the on-disk / in-memory layout.
/**
* @brief On-disk header of a TEMP.GAM save image.
*
* Little-endian, packed (no padding — verify with a static_assert on sizeof).
* Offsets are byte offsets from the start of the file.
*/typedefstructSaveHeader {char magic[4]; /**< 0x00, 4B: "BAK\0" signature. */unsignedshort version; /**< 0x04, 2B: format version (currently 3). */unsignedshort flags; /**< 0x06, 2B: bitfield, see @ref SaveFlags. */unsignedlong timestamp; /**< 0x08, 4B: DOS packed date/time. */unsignedshort partyCount; /**< 0x0C, 2B: number of party members (1..6). */unsignedchar chapter; /**< 0x0E, 1B: current chapter (1..9). */unsignedchar _pad0; /**< 0x0F, 1B: padding / reserved, always 0. */unsignedlong zoneOffset; /**< 0x10, 4B: file offset to the zone block. */
} SaveHeader; /**< total size: 0x14 (20 bytes). */
5. Enum with per-value documentation
/**
* @brief Actor flag bits stored in @ref CombatActor::flags.
*/typedefenumCombatActorFlags {
CAF_NONE = 0x00, /**< No flags set. */
CAF_VISIBLE = 0x01, /**< Rendered this frame. */
CAF_HOSTILE = 0x02, /**< Attacks the party on sight. */
CAF_DEAD = 0x04, /**< HP depleted; awaiting cleanup. */
CAF_SCOUTED = 0x08/**< Party has spotted this actor. */
} CombatActorFlags;
Enum values can also be documented with a preceding /** */ block when they need prose.
6. Union / anonymous members
/** @brief Tagged value read from a record field. */typedefunionRecordValue {long asLong; /**< Interpreted as a 32-bit signed integer. */unsignedchar asBytes[4]; /**< Raw 4-byte little-endian representation. */void far *asPtr; /**< Interpreted as a far pointer. */
} RecordValue;
For an anonymous struct/union member, document the containing member; Doxygen
folds the anonymous fields into the parent.
7. Function with @param / @return / @retval
/**
* @brief Look up a combat actor by id.
* @details Linear scan of the zone actor pool. Empty slots (id 0xFFFF) are skipped.
*
* @param[in] zone Zone whose pool is searched; must be non-NULL.
* @param[in] id Actor id to find.
* @param[out] index On success, receives the pool slot index. May be NULL.
* @return Pointer to the actor, or NULL if not found.
* @retval NULL No actor with @p id exists in @p zone.
* @note O(n) in pool size; callers in a hot loop should cache the result.
* @warning The returned pointer is invalidated by @ref pagedrec_release.
* @see @ref czone_actor_count
*/
CombatActor *czone_find_actor(CombatZone *zone, unsignedshort id, int *index);
Refer to a parameter inside prose with @p id. Direction tags: [in], [out],
[in,out]. Comma-join names to document several at once: @param[in] x, y Coordinates.
8. Grouping into a module (@defgroup + @{ @})
/**
* @defgroup combat_grid Combat Grid
* @brief Tactical 50x50 grid: actor pools, geometry, collision.
* @{
*/
CombatActor *czone_find_actor(CombatZone *zone, unsignedshort id, int *index);
intczone_actor_count(const CombatZone *zone);
/** @} *//* end of combat_grid */
Add a symbol declared elsewhere to the group with @ingroup combat_grid in its block.
@addtogroup combat_grid (with @{ @}) appends to an existing group across files.
@name Grid helpers + @{ @} groups members within one struct/file without a module.
Where a module overview belongs — @file brief + @defgroup narrative
A @file block documents a file; a @defgroup documents a concept/subsystem. A
"what is this module about" overview (the 10,000-foot narrative — purpose and high-level
design, no implementation detail) is a concept, so it belongs in a @defgroup, not
stuffed into a fat @file detailed description. Rules:
Put the overview in the public header, never the .c. The header is the public
contract readers browse; source-file docs organize worse and risk duplicate-@file
warnings. Reserve the .c for @internal implementation notes.
Keep every @file block a one-liner — @brief (one line) + @ingroup <label> so the
file page links to the module. The narrative lives in the group, so all your @file
blocks stay uniform instead of one being fat.
Define the group exactly once. For a single-header module, put the
@defgroup <label> <Title> (carrying the narrative) at the top of that header, right after
the includes, open it with @{, and close with @} before #endif; the header's
declarations become the group's members. If the module later grows a second file, that
file uses @addtogroup <label> — never a second @defgroup.
A whole project's group taxonomy can instead be centralized in one groups.dox holding
every @defgroup, with each header @addtogroup-ing in — reach for that only once you
have many modules and want a curated (nested) tree. Premature for a single group.
One brief, one detailed per entity. Don't split the overview across .h and .c, and
don't give the @file and the @defgroup the same text — they're different entities: the
@file brief names the file, the @defgroup carries the subsystem overview.
Don't use @mainpage or a separate .md (USE_MDFILE_AS_MAINPAGE) for one module —
that's for project-wide front matter.
Single-header module, the canonical shape:
/**
* @file resource.h
* @brief The resource-I/O layer.
* @ingroup resource
*/#ifndef RESOURCE_H#define RESOURCE_H/**
* @defgroup resource Resource I/O Layer
* @brief One-line what-it-is.
*
* The 10,000-foot narrative: purpose, high-level design, what it abstracts — no
* implementation detail. Reference symbols with @ref (e.g. @ref ResFile).
* @{
*//* ... typedefs / externs / function decls ... *//** @} */#endif
Cross-referencing (never hardcode a symbol)
HARD RULE: every reference to a symbol is an @ref. It is the only form Doxygen
verifies, so it is the only form allowed. Autolinks are not a "readable alternative" here —
they are banned for naming a symbol, because a rename orphans them silently.
You want to reference…
Write
Never
A function
@ref worldrender_sprite_billboard
worldrender_sprite_billboard(), bare name
A type (struct/enum/union/typedef)
@ref MeshPolygon
::MeshPolygon, MeshPolygon
A struct/union member
@ref PolyListBlock::bTag
PolyListBlock::bTag, @c bTag
A macro / global / enum value
@ref SHAPE_SCALE_UNITY
bare name
A file
@ref SHAPEBLD.H
SHAPEBLD.H, FILE.C:249
A parameter, in prose
@p vtxCount
vtxCount
Target not yet documented
@c name TBD-ref: <why>
#tbd name (it warns), @ref name (false warn)
Rules:
Use @ref <name> for the symbol itself. For a function, @ref foo (no parens — @ref
renders the name link without them). For a member, @ref Struct::member.
Never a bare autolink (foo(), ::Type, Struct::member) and never @see/@sa for
anything you want the lint to guard — they don't warn when broken (measured below).
Never pin a location. No FILE.C:249, no "at line 249", no "see the loop below."
Reference the symbol (@ref r3d_mesh_part_polygons), which relocates with its code, or
the file (@ref FILE.C). A raw FILE.C:NNN is never acceptable.
TBD-ref is the only sanctioned escape hatch. When a target genuinely isn't in
Doxygen yet (its header lacks @file, or it's undocumented), write @c name plus the
literal token TBD-ref and a reason. It's inert (no warning), greppable, and marks the
spot to upgrade to @ref once the target is documented. Do not reach for #name —
# is a live link operator and warns exactly like @ref.
What the doc-lint actually catches (measured, not assumed)
Only an explicit @ref / \ref (and the #name operator) to a missing target warns
(unable to resolve reference to 'X' for \ref command / explicit link request to 'X' could not be resolved). Everything else that fails to resolve is silent:
Broken reference
Warns?
@ref gone_symbol
yes — fails the lint
#gone_symbol
yes (but use @ref; # is easy to fire by accident)
fn(), ::Type, Struct::member autolinks
no
@see gone() / @sa ::Gone
no
@c name, TBD-ref: name (inert markers)
no — intended
The lint additionally catches, regardless of @ref: @param names that don't match the
signature (WARN_NO_PARAMDOC), malformed @commands, and incomplete docs.
@ref only resolves to a documented target — and @file gates that (ties back to
Golden Rule #1). This is why the @ref mandate must be sequenced. With EXTRACT_ALL = NO, file-scope symbols — functions, globals, macros — are extracted only if their
declaring header has an @file block; compound types (structs/enums/typedefs) and their
members are extracted regardless. So @ref my_func warns as unresolved even though the
function exists when its header is missing @file. Order of operations:
Land @file blocks on every header (grep -L @file **/*.h **/*.H) and grow doc coverage.
Convert references to @ref; use @c name TBD-ref: for anything still undocumented.
Only once the tree lints clean, turn on WARN_AS_ERROR = FAIL_ON_WARNINGS as the gate.
Turn the gate on before step 1 and every undocumented target is a false positive — the lint
becomes noise and gets ignored.
Enforcement knobs (in the Doxyfile):
WARN_NO_PARAMDOC = YES# every @param must match a real parameterWARN_IF_DOC_ERROR = YES# malformed @commands, bad @refWARN_AS_ERROR = FAIL_ON_WARNINGS # any warning → non-zero exit (CI gate)
Lint-only run (parse + warn, no doc output on disk — one cheap generator must stay on to
avoid Doxygen's own "No output formats selected" warning, so aim it at scratch):
Generate a template, then set the C-relevant keys:
doxygen -g Doxyfile # write a default Doxyfile
doxygen Doxyfile # build docs into ./html
Minimal C-oriented overrides:
PROJECT_NAME = "MyProject"OPTIMIZE_OUTPUT_FOR_C = YES# C mode: "Functions" not "Member Functions", etc.JAVADOC_AUTOBRIEF = YES# first sentence = brief (skip explicit @brief)TYPEDEF_HIDES_STRUCT = YES# document `typedef struct {..} Foo;` under the name FooEXTRACT_ALL = YES# document even undocumented items (good while filling in)EXTRACT_STATIC = YES# include file-static functions/varsINPUT = ./src ./include
FILE_PATTERNS = *.c *.h
RECURSIVE = YESGENERATE_HTML = YESGENERATE_LATEX = NO
Notes:
EXTRACT_ALL = YES lists undocumented items too — useful early, but it hides missing
docs. Turn it OFF once coverage is good so gaps surface as warnings.
TYPEDEF_HIDES_STRUCT = YES makes typedef struct {..} Foo; appear as Foo, not as an
anonymous struct plus a typedef.
Gotchas checklist
@file present in every header/source you want file-level symbols from —
otherwise globals, typedefs, and macros are silently undocumented.
/**< */ has the < and comes after the member on the same line (or the next
line). A plain /** */ after a member documents the next item, not the previous one.
Lines wrap at 80 columns — no doc-comment line exceeds 80; overflow a trailing
/**< */ by switching to a preceding /** */ block. Never split an @ref/@p
token across lines (ReflowComments is off, so this is a manual check).
Brief/detail: rely on JAVADOC_AUTOBRIEF (first sentence) or explicit
@brief/@details, not a confusing mix. A blank line separates brief from detail.
C, not C++: set OPTIMIZE_OUTPUT_FOR_C = YES; use typedef struct idioms and
TYPEDEF_HIDES_STRUCT rather than C++ class docs.
Every parameter has a @param line stating its contract (range, units,
nullability, ownership) — not a restatement of its name.
No structural command needed when the block is adjacent to its item. Reach for
@struct/@enum/@union/@var/@typedef/@fn only for detached comments.
\ vs @: both prefixes are equivalent (\brief == @brief). Stay consistent.
HARD RULE — every symbol reference is @ref. Functions, types, members, macros,
globals, files: @ref name (never bare prose, never a fn()/::Type autolink, never
@see/@sa for a guarded ref — those don't warn when broken). ZeroFILE.C:NNN
pins or "see line N" pointers. Undocumented target → @c name TBD-ref: marker, never
#tbd. Sequence: @file/doc coverage first, then the WARN_AS_ERROR gate.