MAME Coding Conventions

From MAMEDEV Wiki

MAME source code should be viewed and edited with your editor set to use four spaces per tab. Tabs are used for initial indentation of lines, with one tab used per indentation level. Spaces are used for other alignment within a line.

Some parts of the code follow Allman style; some parts of the code follow K&R style — mostly depending on who wrote the original version. Above all else, be consistent with what you modify, and keep whitespace changes to a minimum when modifying existing source. For new code, the majority tends to prefer Allman style, so if you don't care much, use that.

All contributors need to either add a standard header for license info (on new files) or inform us of their wishes regarding which of the following licenses they would like their code to be made available under: the BSD-3-Clause license, the LGPL-2.1, or the GPL-2.0.

The guidelines below are not hard-and-fast rules, but more a set of guidelines describing conventions used in MAME source.

Naming

  • class, function, method, and variable names use the lower_under_convention
  • not everyone uses scope prefixes, but if you're going to, please follow this convention:
    • instance member variables should have an m_ prefix (e.g., m_device, m_config, etc.)
    • static members should have an s_ prefix (e.g., s_device_table, etc.)
    • global variables should have a g_ prefix (e.g., g_profiler, etc.)
    • file scope variables (file statics or anonymous namespace) should have an f_ prefix (e.g., f_ntfs_offset, etc.)
    • static member functions may have a static_ prefix (e.g., static_timer_callback)
  • macros, enum items, and #defined constants should be named using the ALL_CAPS_UNDER_CONVENTION
    • static constant members should be treated as constants and should be in ALL_CAPS_UNDER_FORMAT (e.g., MAXIMUM_ITEMS)
  • template parameters should be named using the PascalCaseConvention
  • constants which are part of a group should have a common prefix; example: enum { ADDRESS_SPACE_PROGRAM, ADDRESS_SPACE_DATA, ADDRESS_SPACE_IO };
  • prefer descriptive variable names (sampnum, memoffset) over single-letter names (i, j)

Comments

  • single-line comments should use // C++ line comment style
  • larger block comments should use /* C comment style */ to make editing and reformatting easier
  • interface class and function declarations should ideally have Doxygen-format comments

Spacing

  • indenting should follow scope blocks, one level of indent per scope level
  • use extra indent for statement continuation to make it easily distinguishable from a scope block
  • no space between the function name and opening parenthesis of argument list: std::strlen(param[1])
  • space between flow control keyword and opening parenthesis of expression: for (x = 0; x < 10; x++)
  • no space between unary unary operators and operands: *it++ = -value
  • spaces between binary/ternary operators and operands: flag ? (a + 1) : b
  • no space between parenthesis/bracket and the inner expression: (((i + j) * k) >> m)
  • use blank lines to visually separate logical blocks; use more consecutive blank lines to separate bigger logical blocks

Scoping

  • in general, keep scoping as tight as possible — tighter scope makes code easier to analyse
  • keep members private to protected if possible and provide accessors for externally visible state
  • use protected inheritance if the base class/struct isn't part of the interface
  • declare constants inside the public section of classes they're relevant to
  • restrict local variable scope to parts of the function where they're needed needed
  • use anonymous namespaces for things that shouldn't be visible outside a single source file

Language conventions

  • prefer references over pointers, using pointers primarily in situations where nullptr is a valid option
  • use static and const keywords aggressively where appropriate
  • use std::function for callbacks when performance isn't critical, or MAME delegates when it is
  • wherever possible, use enum instead of a macro
  • used scoped enum (enum class) where a closed set of values are valid
  • wherever possible, use inline functions instead of macros
  • wherever possible, use templates instead of macros
  • macros that look like functions should be wrapped with do { <macrobody> } while (0)
  • use rvalue references and universal references when it could help performance
  • ensure non-POD types used in vectors/deques are movable to avoid performance issues
  • don't abuse auto type — it is useful for template argument dependent types, iterators and proxies, but it can be dangerous and doesn't help readability/maintainability for locals where you know the type
  • use anonymous namespaces to suppress symbol export — it works on all types of symbols including class members, it encourages grouping of non-exported classes/functions/globals, it avoids visual clutter of the static prefix on each declaration/definition, and it reduces overloading of the meaning of the static keyword

Variables

  • avoid declaring static variables inside a function scope — these are really global variables and belong at the top of the module
  • declare all global variables at the top of the file

Header Files

  • the preferred order of definitions in a header file is:
    • standard header
    • reinclusion protection (see below)
    • includes
    • debugging flags
    • constants
    • type definitions
    • macros
    • global variables
    • function prototypes
    • and inline functions
  • function prototypes in header files generally do not use an extern qualifier
  • all header files should support reinclusion; this is done by adding the following to the top of each header
#ifndef MAME_MODULE_FILENAME_H
#define MAME_MODULE_FILENAME_H
#pragma once

and adding

#endif // MAME_MODULE_FILENAME_H

to the end. Note that #pragma once is provided because it may faster when supported. Since not all compilers do support it, the #ifndef/#define methods are retained as a fallback.

Source Files

  • the preferred order of code in a source file is:
    • standard header
    • includes
    • debugging flags
    • constants
    • type definitions
    • macros
    • global variables (both static and global)
    • internal function prototypes
    • inline functions
    • externally referenced functions
    • internal functions (in same order as prototyped)

Back to How MAME Works