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".
التثبيت
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
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.