Skip to content

Latest commit

 

History

History
353 lines (299 loc) · 12.2 KB

CODING_CONVENTIONS.md

File metadata and controls

353 lines (299 loc) · 12.2 KB

Coding Conventions

General

  • Code shall be C11 compliant.
  • Avoid dynamic memory allocation (malloc/free, new, etc.)! It will break real-time guarantees, increase code complexity, and make it more likely to use more memory than available.
  • Avoid the use of floating point arithmetic. Not every MCU has a FPU and software floating point libraries cause unnecessary overhead. Instead use fixed-point integers and transform equations so that they stay within the range of integer math. An easy way to ensure this is by multiplying by a constant factor, ideally a power of two - this is a simple shift operation. Take care that intermediate values do not exceed the range of the data type you are using. When writing drivers, do not convert the measurement data into float, but instead choose an appropriate integer format / SI prefix.
  • Please obey the Linux coding style as described in https://www.kernel.org/doc/Documentation/process/coding-style.rst with the following modifications and additions:
    • Line length: aim for no more than 80 characters per line, the absolute maximum should be 100 characters per line.
    • All line endings shall be set to LF (\n). (How to handle line endings in Git: https://help.github.com/articles/dealing-with-line-endings)
    • There must be no trailing whitespace in any line. The script /dist/tools/whitespacecheck/check.sh main || exit can be used to detect whitespaces at the end of line(s) that would lead to build error(s).
    • Use empty braces for empty while loops waiting for a hardware register instead of semicolon. while (HW_STATUS != STATUS_OK) {} is correct, while (HW_STATUS != STATUS_OK); is wrong.
    • Our policy regarding typedefs is completely different (see below) (BTW: Do we have any reason to do so?)
    • Comments should be C-style comments (see below)
  • In order to follow Linux's recommendation on conditional compilation make use of IS_ACTIVE and IS_USED macros from kernel_defines.h with C conditionals. If a symbol is not going to be defined under a certain condition, the usage of preprocessor #if defined() is fine.
  • You can use uncrustify with the provided option files: https://github.com/Mesh4all/m4a-firmware/blob/main/uncrustify-riot.cfg

Types

  • Be careful with platform dependent type sizes like int or long. Use data types that include the bit length in the name like uint16_t when you need to make sure that a certain variable is exactly of this length.
  • The use of typedefs for structs and pointers is allowed.
  • Type definitions (using typedef) always end on "_t".
  • If a typedef is used for a struct, it has to be specified at the struct definition (i.e., not as a separate line). E.g.:
    typedef struct {
        uint8_t a;
        uint8_t b;
    } foobar_t;
  • Use of a separate line typedef for structs is allowed for forward declarations, e.g.,
    typedef struct mystruct mystruct_t;
    [...]
    struct mystruct {
         [...]
    };
  • Guidelines for pointer types (as long as it is reasonable):
    • use char * for strings and only for strings
    • use uint8_t[] as type for arbitrary byte buffers, but use void * to pass them around. uint8_t[] because we're dealing with bytes and not characters, void * to avoid unnecessary casting shall the need arise to have their content to have a certain type
    • use uint8_t * to pass "typed" byte buffers, e.g., link-layer addresses, where it avoids unnecessary temporary variable
    • use void * for generic typing

Variables

  • Do NOT use global variables unless it is unavoidable.
  • If you declare a variable within a header file, you MUST use the keyword extern.

Functions

  • Every function needs a prototype in addition to its definition. If a prototype is specified within a .c file it has to be declared BEFORE any function definitions.
  • If the scope of a function is limited to one file, it MUST be declared static.
  • Functions without parameters must be specified with (void).
  • Keep functions short! As a rule of thumb, the function's body should not exceed one screen.
  • Do NOT use global macros defining more than one line of code. Use inline functions instead.

Return values

  • Every function must return one of the following values or none (void):
    • logical value (zero or not zero)
    • an error code (given as a negative number or zero) or a positive status value
    • the count of read or written bytes/values for I/O functions
    • the position or address (for search functions)
    • a pointer
  • NULL indicates an error case, too.
  • Do NOT return structs or other larger types! These would get copied to the stack, resulting in expensive operations. Moreover, some compilers have trouble with larger return types. Use pointers to structs instead and take care of the structs lifetime.
  • If possible, prefer signed types over unsigned ones in order to be able to add error codes later on.

Naming

  • Names of all public functions and variables must start with the name of the corresponding library, e.g.:
    thread_getpid(void);
    hwtimer_init_comp(uint32_t fcpu);
    int transceiver_pid;
  • Private functions and variables do NOT have this library prefix.
  • Do NOT use CamelCase. Function, variable and file names as well as enums, structs or typedefs are written in lowercase with underscores.
    /* instead of: */
    void CamelCaseNamedFunction(int camelCaseNamedVar);

    /* write: */
    void camel_case_named_function(int camel_case_named_var);
  • When implementing constants or variables that are defined in third party documents such as RFCs, add a prefix to those names based on the RIOT coding conventions. If you use a name in the RIOT code that is different from the one in the third party document, you must add a reference to the original name of the constant or variable in the Doxygen documentation.

Indentation and braces

  • Indentations are four spaces (i.e., NO tab characters).
  • As an exception to the Linux coding style, the closing brace is empty on a line of its own when followed by an else, too. When followed by a while in a do-statement, it goes into the same line.
  • Use curly braces even for one-line blocks. This improves debugging and later additions.
    /* instead of: */
    if (debug) println("DEBUG");
    else println("DEBUG ELSE");

    /* write: */
    if (debug) {
        println("DEBUG");
    }
    else {
        println("DEBUG ELSE");
    }
  • Commas are always followed by a space.
  • For complex statements it is always good to use more parentheses - or split up the statement and simplify it.

Includes

  • The include of system headers (in <>-brackets) always precedes RIOT specific includes (in quotes).
  • Optional headers must only be included if their corresponding module is selected/being build. In other words: always put an #ifdef MODULE_... statement around includes of optional headers:
#ifdef MODULE_ABC
#include "abc.h"
#endif

Header Guards

All files are required to have header guards of the form

#ifndef PATH_TO_FILE_FILENAME_H
#define PATH_TO_FILE_FILENAME_H

...
#endif /* PATH_TO_FILE_FILENAME_H */

Rules for generating the guard name:

  1. take the file name
  2. if there's include/ in the file's pathname, include the path from there on.
  3. replace "/" and "." with "_"
  4. convert to uppercase letters
  5. if the produced guard starts with "_", prefix "PRIV"

Examples:

  • "core/include/msg.h" -> "MSG_H"
  • "sys/include/net/gnrc/pkt.h" -> NET_GNRC_PKT_H
  • "drivers/abcd0815/abcd0815_params.h" -> ABCD0815_PARAMS_H
  • "sys/module/_internal.h" -> PRIV_INTERNAL_H

Note: these rules will be enforced by the CI.

C++ compatibility

  • C Header files should be always wrapped for C++ compatibility to prevent issues with name mangling, i.e. mark all the containing functions and definitions as extern "C"
#ifdef __cplusplus
extern "C" {
#endif

... all your function declarations, global variables and defines belong here

#ifdef __cplusplus
}
#endif
  • use __restrict instead of restrict in headers

Absolute values

  • Absolute values must be specified as macros or enums, not as literals, i.e. instead of
int timeout = 7 * 1000000;

write

int timeout = TIMEOUT_INTERVAL * USEC_PER_SEC;

Comments

  • All comments should be written as C-style comments.

E.g:

/* This is a C-style comment */

Wrong:

// C++ comment here

Documentation

  • All documentation must be in English.
  • All files should contain the copyright note.
  • Doxygen documentation is mandatory for all header files.
  • Every header file includes a general description about the provided functionality.
  • Every function must be documented - including parameters and return value.

An examplary doxygen documentation in a header file can look like this.

/*
 * Copyright (c) 2022 Mesh4all <mesh4all.org>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 * @ingroup     foobar
 * @{
 *
 * @file
 * @brief       Definitions for foo and bar functions.
 *
 * More detailed information about the file and the functionality implemented.
 *
 * @author      your_name  <your_email_address@mail.com>
 *
 */

/**
 * @brief   Set the state of foobar.
 *
 * @param[in]  state      The new state of foobar.
 * @param[out] old_state  The old state of foobar is written to this variable.
 *
 * @return 1 if setting the state was successful, 0 otherwise.
 */
 int set_foobar(int state, int *old_state);

Common compilation warnings

Some solutions to correctly handle compilation warnings.

-Wformat

Solution for string formatting errors:

  • When printing a size_t

    • use %u and cast the variable to (unsigned) because newlib-nano does not support %zu
  • When printing an unsigned char/uint8_t

    • Use %u because newlib-nano does not support %hu/PRIu8
  • When printing an uint32_t

    • Use PRIu32 print format
  • When printing 64bit variables

    • It is not correctly supported by newlib-nano, use fmt module for these in this case.

-Wformat-nonliteral

For a printf style function with the following error: error: format string is not a string literal.

  • Function using a variable number of arguments:
    • Use __attribute__((__format__ (__printf__, 3, 4))), where here 3 is the number of the argument with the format and 4 the format arguments, starting from 1.
  • Function using va_list:
    • Use __attribute__((__format__ (__printf__, 1, 0))), where here 1 is the number of the argument with the format and 0 as there is no variable numbers of arguments.

Git

  • Make one commit per change.
  • The first line of the commit message describes the main feature of the commit.

Continuous Integration

  • If the CI tests fail due to errors these errors need to be addressed.
  • If the CI tests fail due to warnings/errors emitted by cppcheck you should try to fix the error. If the error is definitely a false positive there is the possibility to suppress this warning/error. You MUST do so by adding a comment, including a rationale why it is a false positive and why the code can't be fixed otherwise, in the following format:
    /* cppcheck-suppress <category of error/warning>
     * (reason: cppcheck is being really silly. this is certainly not a
     * null-pointer dereference */

Welcome to Mesh4all.
It's time to join everyone to the big mesh.