Skip to content

Source Code Style Guidelines

Jiri Malak edited this page Feb 20, 2021 · 11 revisions
Table of Contents

Introduction

Open Watcom products are usually written mostly in C, but there is also a substantial body of C++ code and a small amount of assembler for various platforms. This document describes the style conventions used by Open Watcom contributors for all Open Watcom projects, and is intended to be used as a guideline if you are going to be modifying the source code in any way. Any contributed code that is to be included in a Open Watcom product must follow these style guidelines; submissions violating these guidelines will be rejected.

It is important to have all source code formatted in a similar manner as this helps to give the entire product a cohesive image, and makes finding one’s way around the source code much easier. Because C and C++ are entirely free-form, there are many different ways that the source code can be formatted. All programmers have their own preferred formatting style, just as all writers have their own personal writing style. However, just as when multiple authors work on a single book, multiple programmers working on a common software product should adhere to consistent formatting and style conventions.

This document is intended to serve as a guide to the style guidelines used by Open Watcom developers. If, however, something is not clearly documented here, the best reference to the style guidelines is to look at some Open Watcom source code.

NB: The Open Watcom code base is very large (over two million lines of C/C++ source code) and was written by many people over the period of over 20 years. As a result, not every source file conforms to these guidelines. You may wish to reformat existing non-conforming source files, but if you do so, please observe the following: it is highly recommended that you reformat an entire project or subproject instead of a single source file, and never mix formatting changes with functional changes. Always submit formatting changes separately, as it makes source code maintenance much easier.

Indentation and Tab Width

All source code should be formatted to use 4-space tabs, and all indentation should be in multiples of four spaces (one indentation level). The source code should be formatted using only real spaces and never hard tabs, so you will need to configure your editor to use 4 space tabs and to insert spaces only into the source code. Do NOT use 8-space hard tabs in your source code! If you absolutely must work with 8-space hard tabs, please run the resulting source code through a tab expansion utility. Note that this also includes assembly code. Only files that absolutely require hard tabs (notably GNU makefiles) are exempt from this rule.

Source Code Width

All source code should try to use a maximum of 80 characters per line. Some editors can only display 80 characters on a line, and using more can cause them to exhibit line wrap. This is only a guideline, and if more than 80 characters will enhance the legibility of the code, use wider source lines---but not too much wider. Lines longer than 100 characters are supect and more than 130 columns is absolutely out of the question.

C/C++ Source Code Formatting

This section describes the conventions used for formatting C and C++ source code modules, and the conventions that you should follow if you modify or extend any of the source code. All C source code should be contained in text files with the .c extension, and all C++ source code should be contained in text files with the .cpp extension. It is highly recommended that all source file names are lowercase and following the 8.3 convention for maximum portability. Files that are not lowercase are only acceptable if there are externally imposed requirements for them; it is never allowed to have two files in the same directory whose names only differ in case.

Source File Layout

The overall layout for source code is important to help developers quickly find their way around unfamiliar source code for the first time. All C and C++ source code modules should be formatted in the following manner:

/****************************************************************************
*
*                            Open Watcom Project
*
* Copyright (c) 2002-2019 The Open Watcom Contributors. All Rights Reserved.
*    Portions Copyright (c) 1983-2002 Sybase, Inc. All Rights Reserved.
*
*  ========================================================================
*
*    This file contains Original Code and/or Modifications of Original
*    Code as defined in and that are subject to the Sybase Open Watcom
*    Public License version 1.0 (the 'License'). You may not use this file
*    except in compliance with the License. BY USING THIS FILE YOU AGREE TO
*    ALL TERMS AND CONDITIONS OF THE LICENSE. A copy of the License is
*    provided with the Original Code and Modifications, and is also
*    available at www.sybase.com/developer/opensource.
*
*    The Original Code and all software distributed under the License are
*    distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
*    EXPRESS OR IMPLIED, AND SYBASE AND ALL CONTRIBUTORS HEREBY DISCLAIM
*    ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF
*    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR
*    NON-INFRINGEMENT. Please see the License for the specific language
*    governing rights and limitations under the License.
*
*  ========================================================================
*
* Description:  Description of the code module. This should describe the
*               purpose of the code in this module.
*
****************************************************************************/


#include "myhdr.h"      /* Include any necessary headers */

/* Global variable declarations and static variables with file scope */

/* All functions implemented in this module. Where possible static or
 * private functions should be located before functions that use them to
 * avoid the need for superfluous function prototypes. However all functions
 * *MUST* have a prototype, so if necessary add local prototypes to the top
 * of the file before the regular functions begin.
 */

Function Body

The body of a C or C++ function should be written with the open brace that starts the body of the function in the leftmost column, and the closing brace in the same column. All source code statements contained within the function body should start at the first indentation level (four spaces in). The name of the function and an opening '(' should be on the first line, with the return type preceding the function name.

If the function is static or private, it may be left without any preceding documenation provided the function is short. However any function that contains more than about 5 lines of source code and all public functions should be preceded with a source code comment. An example of a properly formatted function with documentation header is as follows:

// Concise description of the function.
// May use C or C++ style comments.
bool my_function( int arg1, int arg2 )
{
    // function body
}

There is no single standard for commenting functions used throughout the Open Watcom source tree. However, two common commenting styles are shown below:

/*
 * my_function - description of the my_function function, which may span
 *               multiple lines if necessary
 */
bool my_function( int arg1, int arg2 )
{
    // function body

} /* my_function */
bool my_function( int arg1, int arg2 )
/************************************/
{
    // function body
}

Generally, it is a good idea to use the same commenting style throughout any particular module.

If a function has too many arguments to comfortably fit on a single line, the line needs to be broken up appropriately. Recommended formatting is as follows:

void my_long_function( first_complex_type *arg1, second_complex_type *arg2,
                       int arg3, third_complex_type *arg4 )
{
    // function body goes here
}

Note that a C language function which takes no parameters should be declared as type foo( void ) rather than type foo(). The former says foo has no parameters; the latter that parameters are unspecified. When declaring a C++ function that takes no arguments or calling a function in C/C++ with no arguments there should be no space between the parenthesis ().

Several projects in the source tree use capitalisation rather than underscores to separate the words. Continuity within a project is important. In this case the first word is not capitalised for a function local to that module and is capitalised for an exteral function.

void MyFunction( void );  // external to module
void myFunction( void );  // local to module

Flow Control Statements

All the flow control statements should have the open brace placed on the same line as the start of the flow control statements, and all statements that are part of the body of the flow control statement should be indented by one level (four spaces). The closing brace should be indented to be at the same level as the flow control statement keyword. This helps to partition the statement into the declaration section and the body section of the statement. Note also that for improved legibility, the flow control statements should be formatted with a single space after the opening parenthesis and another before the closeing parenthesis, as well as a space before the opening brace. For example:

if( condition ) {
}

rather than the less legible

if(condition){
}

Where the dependent statement does not require braces, it may be, in order of preference, either:

  • be given braces it does not strictly require

if( condition ) {
    consequence;
}
  • indented on the next line

if( condition )
    consequence;
  • placed on the same line

if( condition ) consequence;

The last form is not recommended for practical reasons. If a conditional statement is formated like

if( condition ) do_something();

it is not immediately obvious when stepping over the line in a debugger whether do_something() was executed or not. Hence if this form is used, it should only ever be done with control constructs such as return, break or continue. Even so, it is impossible to put a breakpoint on the conditional statement in a line oriented debugger, therefore this form should be avoided.

The following shows the templates to use for formatting each of the flow control structures in C and C++.

If-else Statements

if( x < foo ) {
    // do something;
} else {
    // do something else;
}

If you wish to use multiple if-else statements strung together, do so like this:

if( x < foo ) {
    // do something;
} else if( x < bar ) {
    // do something else;
} else {
    // default case;
}

When you have an if-else statement nested in another if-else statement, put braces around the nested if-else. For example:

if( foo ) {
    if( bar ) {
        win();
    } else {
        lose();
    }
}

rather than the less legible:

if( foo )
    if( bar )
        win();
    else
        lose();

While Loops

while( x > 1 ) {
    // do something;
}

Do-while Loops

do {
    // do something;
} while( x > 1 );

For Loops

for( i = 0; i < 10; ++i ) {
    // do something;
}

Note that with the for statement, there is no space before the semicolons, and there is always a single space after the semicolon. For example avoid formatting your statements like this:

for(i=0;i<10;i++){
}

If you wish to include multiple assignments in the opening statement of the for statement, or multiple operations in the counter section of the for statement then use the comma operator. However ensure that a space always follows the comma:

for( i = 0, j = 0; i < 10; ++i, j += 10 ) {
}

Do not try to cram too much into the controlling statement of the for loop. For instance the following code (not a contrived example, unfortunately) is not admissible under any circumstances and may be considered a capital offence in some countries:

for(RamAddr=_uldiv(__nvram.CMapShadow.CMapTable[pIO->Selector].Offset+
    pIO->Offset,__nvram.WinSize),Copied=0U,pApp=(PBYTE)pIO->Data;
    Copy=(USHORT)min(__nvram.WinSize-RamAddr.rem,(ULONG)pIO->Size);
    pIO->Size-=Copy,Copied+=Copy,RamAddr.quot++,RamAddr.rem=0UL,
    pApp=pApp+Copy)
    {
        /* loop body */
    }

The rule of thumb is that if the controlling statement of a for loop is longer than a single line, it is too long.

for( ;; ) {
    // do something;
}

should be used instead of the alternative

while( 1 ) {
    // do something in a not so nice way;
}

The rationale is that some compilers may warn on the latter form because the condition is always true.

Switch Statements

switch( x ) {
case 1:
    // code for case 1
    break;
case 2:
    // code for case 2
    break;
default:
    // default case
}

case and default are viewed as part of switch for indentation purposes.

If the code for the cases in the switch statement is exceptionally short, you can format the entire case on a single line, such as the following:

switch( x ) {
case 1: /* tiny code */;   break;
case 2: /* more code */;   break;
}

If you wish to create a nested code block for a case statement (for example to declare some local variables in C code), do so like the following:

switch( x ) {
case 1:
    {
        int local_var;
        // code for case 1
    }
    break;
    ...
}

It is advisable to include a default action for a switch statement, but this may be left out.

If falling through is used in switch statements, a comment to that effect must be added, such as this:

switch( x ) {
case 1:
    // code for case 1
/* fall through */
case 2;
    // code for case 2
    break;
default:
    // default case
}

Goto Statements

Occasionally, it is reasonable to use goto statements. The corresponding labels should be indented to the same level as the braces enclosing both goto and label.

switch( argv[0][1] ) {
...
case 'D':
    if( !optionsPredefine( &argv[0][2] ) )
        goto errInvalid;
    break;
...
default:
errInvalid:
...
}

Usage of gotos should be kept to minimum and is generally not recommended, although the objections are purely practical (difficult to understand and maintain, dangerous) and not religious.

Expressions

When formatting expressions, try to use spaces and parentheses to enhance the readability of the expression---splitting it over multiple lines if necessary to make it more readable. Do not format expressions without any spaces between binary operators and their operands. For example, write

x = (y + 10) - 240 * (20 / (t + my_func()));

rather than

x=(y+10)-240*(20/(t+my_func()));

If parentheses contain an expression, there should be no space after the opening and before the closing parenthesis. Combined with the preceding guidelines, code should be formatted this way:

if( (a || my_func( b )) && ((c = my_other_func( 1 + x )) == 42) ) {
    // do something
}

That is, for parentheses that are parts of control statements and function calls, spaces should be after the opening and before the closing parenthesis, but spaces need not be used with parentheses that simply group expressions together.

The parentheses following the TEXT and _T prefixes commonly used to specify strings in Win32 code should be considered to be instances of this rule and should not have spaces around the string, even though these prefixes are implemented as macros. For example, write

const TCHAR szString[] = _T("Some Text");

Return Statements

In functions that return a value, the return expression should be placed in parentheses. For example:

return( i + 1 );

or

return( TRUE );

In other words, format return statements as if return was a function.

Operator sizeof

The same applies to the sizeof operator, that is, parentheses should always be used as if sizeof was a function (which it of course isn’t). For example:

i = sizeof( void * );

Variable Declarations

Don’t declare multiple variables in one declaration that spans lines. Instead, start a new declaration on each line. For example, instead of

    int foo,
        bar;

write either this (recommended form):

    int foo;
    int bar;

or, if you are lazy, this:

    int foo, bar;

Global Variables Within a Module

Any global variables and external global variables must be declared at the top of the source file. Global variables that are private to the source module should always be declared with static storage. External global variables should always be declared in a separate header file, and the definition in a single source module. When formatting global variables, aligning the names of the variables on tab stops is highly recommended. For example:

// 45678 1 234567  column counter for illustration.
int         my_var1;
long        my_var2;
my_struct   my_structure = {
    ...
};

Local Variables

Declarations of local variables should be formatted similarly to those of global variables, as detailed above. For example:

void foo( void )
// 45678 1 234567  column counter for illustration.
{
    int         my_var1;
    long        my_var2;
    void const  *my_ptr;

    // code follows here
}

Note that the declaration block is separated from the first statement in the function by a blank line. The register storage class specifier should not be used because nearly all compilers will ignore it, and the auto storage class should not be used either as it is superfluous in the presence of a type specifier (which is required by ISO C99, although it was not in earlier language revisions).

Arrays

When an array is dimensioned or accessed, the subscript should be specified with no space to the right of the left square bracket or to the left of the right square bracket. For example:

int array[5];
do_something( array[3] );

Preprocessor Directives

When a series of preprocessor directives (#define, #include, etc.) is conditionally processed, the directives between #if or #ifdef and the corresponding #endif should indented four or two spaces. For example:

#ifdef A_SYMBOL
    #ifdef ANOTHER_SYMBOL
        #define YET_ANOTHER_SYMBOL
        #include <hdr1.h>
    #endif
    #ifdef A_THIRD_SYMBOL
        #include <hdr2.h>
    #else
        #include <hdr3.h>
        #undef A_SYMBOL
    #endif
#endif

The above only applies for source text that consists solely or chiefly of preprocessor directives. When conditional compilation is used to prevent duplicate inclusion of an entire header file (idempotency lock), the code in that header file should not be automatically indented. Also, when extern "C" { and } are placed in #ifdef __cplusplus blocks, they need not be indented.

For conditional compilation of code, the rules are somewhat different. In most cases, preprocessing directives should start at the leftmost column and should not affect the indentation of actual code. For example:

void foo( int b )
{
    if( b > 3 ) {
        do_stuff();
#ifdef __NT__
        do_nt_stuff();
#elif defined( __OS2__ )
        do_os2_stuff();
#endif
        do_more_stuff();
    } else {
        do_something_else();
    }
}

Comments and Commenting Style

Commenting code is extremely important. Comments serve as an aid to the human readers of source code; they are a form of engineering etiquette. Comments should always be used to clarify otherwise obscure code. Do not, however, comment too liberally. If the code itself describes concisely what is happening, do NOT provide a comment explaining the obvious! Comments should always be neat and readable - haphazardly placed comments are almost as bad as haphazardly formatted source code.

Comments should also be used to document the purpose of obscure or non-trivial functions (a non-trivial function is one of more than a few lines) and what inputs and outputs the function deals with. Please refer to the section on Function Bodies for detailed information on the layout and formatting for function headers. The following shows examples for commented source code, and how the comments should be laid out:

void myFunc( void )
{
    int x1 = 10;    /* Comment describing X1's purpose if necessary */

    /* Open up the file and write the value X1 to it */
    ...

    /* Now do some stuff that is complicated, so we have a large block
     * comment that spans multiple lines. These should be formatted as
     * follows for C and C++ code.
     */
    ...

    // One line comments may be like this
    ...

    // C++ style comments are also allowed, but be aware that not all
    // C compilers may accept them; this could be an issue when porting
    // to new platforms.
    ...

C Header File Formatting

This section describes the conventions used for formatting C header files, and the conventions that you should follow if you modify or extend any of the source code. All C header files should be contained in a text file with the .h extension. Note that automatically generated header files usually use a .gh extension, but those files are naturally not intended to be edited directly.

Header File Layout

The overall layout for header files is important to help developers quickly find their way around unfamiliar source code for the first time. All C header files should be formatted in the following manner:

/****************************************************************************
*
*                            Open Watcom Project
*
* Copyright (c) 2002-2019 The Open Watcom Contributors. All Rights Reserved.
*    Portions Copyright (c) 1983-2002 Sybase, Inc. All Rights Reserved.
*
*  ========================================================================
*
*    This file contains Original Code and/or Modifications of Original
*    Code as defined in and that are subject to the Sybase Open Watcom
*    Public License version 1.0 (the 'License'). You may not use this file
*    except in compliance with the License. BY USING THIS FILE YOU AGREE TO
*    ALL TERMS AND CONDITIONS OF THE LICENSE. A copy of the License is
*    provided with the Original Code and Modifications, and is also
*    available at www.sybase.com/developer/opensource.
*
*    The Original Code and all software distributed under the License are
*    distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
*    EXPRESS OR IMPLIED, AND SYBASE AND ALL CONTRIBUTORS HEREBY DISCLAIM
*    ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF
*    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR
*    NON-INFRINGEMENT. Please see the License for the specific language
*    governing rights and limitations under the License.
*
*  ========================================================================
*
* Description:  Description of the header file. This should describe the
*               purpose of this header file.
*
****************************************************************************/


#ifndef HEADER_H_INCLUDED   /* Include guard. The #define name should be   */
#define HEADER_H_INCLUDED   /* the same as the header (ie: header.h here). */
/* HEADER_H_INCLUDED would be in the user's "namespace". Implementation specific
 * headers distributed to end-users have to use reserved names, e.g.
 * #ifndef _STDLIB_H_INCLUDED
 * #define _STDLIB_H_INCLUDED
 * ...
 * #endif
 * Only system headers (ie. headers not intended to be built with any other
 * compiler) should use reserved names.
 */

#include <stdio.h>      /* Include any dependent include files */

/* If necessary, declare structure packing; The _Packed keyword may be
 * used on individual basis instead of the #pragma.
 */
#pragma pack( push, 1 )

/* Macros, enumerations, type definitions, structures and unions should be
 * defined in this section for both C and C++ code.
 */

/* Restore the previous packing value if it has been changed */
#pragma pack( pop )

/* The following is not stricly required for header files that are
 * never to be included from C++ code.
 */
#ifdef  __cplusplus
extern "C" {            /* Use "C" linkage when in C++ mode */
#endif

/* Include C function prototypes in here */

#ifdef  __cplusplus
}                       /* End of "C" linkage for C++ */
#endif

#endif  /* HEADER_H_INCLUDED */

Enumerations

Enumerations should be defined for all numerical constants used in header files, rather than using a macro #define or a set of constants. The reason for this is that it provides a logical grouping for related constants, and perhaps more importantly, eases debugging (because the debugger knows about enumerations but doesn’t know about macros). The format for defining an enumeration typedef is as follows:

typedef enum {
    CHANGE_NORMAL   = 0x00,
    CHANGE_ALL      = 0x01,
    CHANGE_GEN      = 0x02,
    CHANGE_CHECK    = 0x04
} change_type;

Structures and Unions

It is preferred to declare structures and unions using a typedef, so that the structure can be used without requiring the struct or union keyword. For example:

typedef struct {
    ...
} score_reg;

typedef union {
    ...
} symbol;

rather than:

struct score_reg {
};

union symbol {
};

For C++ code however, you should declare structures and unions without using the typedef keyword, since the language treats these constructs differently.

Function Prototypes

All public functions must be declared in only one header file, in the 'function prototypes' section of the header file. For C callable functions (even if implemented in C++) the function prototypes must be bracketed with extern "C" as shown in the sample header file above. This is to ensure that all global functions will be callable from both C and C++ code. Each public function should be declared on a single line.

Although it may seem strange to format functions prototypes in this manner, the C/C++ compiler will do the necessary job of checking that the parameters in the prototype are correct with respect to the function body, and having a single function per line makes it easy to browse header files to find a specific function prototype. Note also that this is one case where you must violate the 80 characters per line guideline and not break the function prototype across multiple lines. For instance a couple of prototypes might declared as follows:

extern bool          my_function( int arg1, int arg2 );
extern my_structure *my_function2( int arg1, int arg2, int arg3 );

Global Variables

Any public global variables must be declared in a header file. When formatting global variable declarations, format them so that the names of the variables align on tab stops. For example:

// 45678 1 2345678 2 2345678
extern int          my_var1;
extern ulong        my_var2;
extern score_reg    my_structure;
extern long         *my_pointer;

C++ Specific Formatting

This is WIP. Various bits will need to be filled in as we come to a consensus.

Header File Layout

TBD - just same layout as Header File Layout?

Namespaces

  • Always comment the close of a namespace

  • Indentation TBD

  • Standard names used for OW extentions and OW internals TBD

namespace std{
} // namespace std

Classes

TBD - should non-standard classes be written TheClass or the_class?

TBD - ditto for member functions?

Blocks of related member fuctions, typedefs, etc should have their names aligned so differences and simiularities can be seen more easily.

For small inline functions, place the function body within the class declaration. For inline functions that are more than about 2 lines long, please place the body of the function after the class definition in the same header file.

Generally try to place a single class definition into a single header file. It may be more convenient in some instances to place multiple class definitions into a single header file, for example, small, logically related classes such as a node class and the list class that uses the nodes.

The class should be ordered TBD. If it defined by a standard it should follow the ordering in the standard as closely as posible.

class MyClass : public Base {
protected:
    int myVar;              // Describe what this is for

    // Comment for the function
    virtual void myProtectedFunction();

public:
    // constructors and destructor first
    MyClass();
    ~MyClass();

    // align related funtions to highlight differences and simularities
    void        insert( int value, int n );
    long_type   insert( const char *str, int n );
    bool        remove( int value );

    iterator        begin();
    const_iterator  begin() const;
    iterator        end();
    const_iterator  end() const;

    // Always document virtual functions
    virtual void myPublicVirtual();
    friend  void myPublicFriend();

    inline void smallInlineFunction() { // function body; };

    inline void mediumInlineFunction()
        { // function body; };

    inline void largeInlineFunction()
        {
            // first line of function
            // second line of function
        };

    inline void reallLargeInlineFunction();
    // body is defined outside of class
};

Templates

The parameter list for a template should be formatted, if possible, entirely on a single line. It is recommended that a space be placed immediately after the opening '<' and immediately before the closing '>'. This makes it less likely that the unexpected tokens will be produced (particularly '>>' or '<:'). It is also consistent with the way functions are formatted according to this guide.

template< class T, class U >
class MyClass {
    ...
};

Usually template functions and member function definitions should be formated simularly to normal functions:

template< class Type, class Allocator >
void list< Type, Allocator >::swap( list &other )
{
    ....
}

template< class InputIterator, class Function >
Function for_each( InputIterator first, InputIterator last, Function f )
{
    ...
}

It is probably best to avoid unnecessarily long parameter names but if the list is long a split will be necessary. Return types can get very long and in this case should be put on a separate line. An example is shown below.

template< class T,
          class ALongTypeParameter,
          class S,
          class AnotherType = WithDefault< T > >
complicated_template< T, ALongTypeParameter, S, AnotherType >::return_type
complicated_template< T, ALongTypeParameter, S, AnotherType >::function(
    const int i,
    size_type pos,
    S &s,
    const AnotherType &a )
{
    ...
}

Function Headers

In my opinion there is a need for some sort of function header comment. This is mainly due to the amount of time needed to read some C++ template member functions.

TBD put example of it here

This section details recommended programming practice as well as some common pitfalls and gotchas. Following these suggestions is quite likely to save programmers much time and frustration.

Don’t Assume

Don’t make too many assumptions about the language or environment. If, say, ISO C does not specify something, do not rely on any particular implementation. This includes, but is not limited to, type sizes, char signedness, structure alignment, enumeration sizes, byte order, and many, many other items. Some of these are discussed in greater detail below.

Adherence to Standards

Programs should be written to comply with the most general standard that provides desired functionality. For instance, on the Win32 platforms, there are three file I/O models available:

  • Standard C library streams

  • POSIX style file handle based I/O

  • Win32 API calls

For most purposes, ISO C library stream functions are sufficient and perform very well, therefore should be used. Only if a feature not provided by the ISO C library is required, or the library for some reason cannot be used, either POSIX or Win32 file I/O (in that order) should be used.

In general, ISO C should be used wherever possible. Portable POSIX interfaces should be used where ISO C is insufficient (and to the extent that the POSIX functionality is supported by Open Watcom on all platforms). Platform specific APIs should only be used as a last resort.

Compiler Warnings

Nearly all of the Open Watcom subprojects are set up to build with maximum warnings and treat warnings as errors. That is a defence against programmers trying to write sloppy code. Reducing the warning level is out of the question and explicitly disabling warnings is frowned upon. Nearly always there is a way to write code so that no warnings are reported. In the extremely rare cases where that might not be possible, a discussion about the usefulness of such warning is warranted.

Compiler Extensions

In general, code should be written with minimal reliance on compiler extensions as long as it is intended to be portable. This means that for instance the C compiler itself should be written in a portable manner, but eg. the DOS specific portions of the runtime library need not be. In some cases (eg. the Win386 extender) no other compiler comes close in capabilities and Open Watcom specific extensions may be used freely — simply because there is no danger of porting that code to any other compiler or platform.

In cases where the use of an extension (such as #pragma aux) can substantially improve otherwise portable code, it can be used, but an alternate codepath needs to be provided for other compilers and/or platforms.

Type Selection

There is an art to selecting basic types in C and C++. For writing portable code, there are two simple rules:

  • use specific width types if required

  • use generic types in all other cases

That is, if specific types are imposed by file formats, networking protocols, or hardware, always use types with explicitly specified width. The C99 types such as uint32_t, int16_t, etc. are recommended. Never ever assume that int or long has specific width.

However, in other cases, select a suitable type sufficient to hold the range of expected values, usually int or long.

Also do not assume that pointers have specific width. Use the C99 uintptr_t or intptr_t types if you need to convert between integers and pointers.

In general, do use types designed for specific applications, such as size_t, off_t, pid_t, etc.

Types and Conversions

Programmers need to have a good understanding of the C type system. That is the only way to ensure that the type system will work for you, not against you.

Keep in mind that a constant such as 123 will have type int, which has different size on different platforms. Constructs such as

long l = 1024 * 1024;

may not do what you expect and explicit L suffix is necessary. Same goes for arithmetic involving variables - explicit casts may be required to achieve desired results.

On the other hand, casts should not be used indiscriminately as they tend to obscure the purpose of code. Only use explicit casts where the default conversions don’t do the job.

Be extremely careful with functions that take variable number of arguments. Keep in mind that default promotions will be applied to the variadic arguments.

Use of 'const'

Use the const modifier where applicable. Use it especially for functions that take pointer arguments but do not modify the storage that the argument points to. This is often the case with many text processing functions that take strings as arguments but do not modify them. The const modifier may help the compiler take advantage of additional optimization opportunities, and more importantly it helps programmers. If a programmer sees that a function takes a pointer to const char, he or she will know that the string won’t be modified so it’s not necessary to create a copy and it could be stored read-only memory.

Use of 'static'

Use the static storage class liberally. It is important for both the compiler and the programmer. The compiler may take advantage of additional optimization opportunities; for instance, a static function that is always inlined need not be emitted in its non-inline form. Keeping functions and variables static also helps prevent name space pollution, as there is no danger that objects with static storage might conflict with objects defined in other modules. Static objects also greatly ease maintenance, because when modifying a static variable or function, the maintainer must only examine the module where the object is defined, without worrying about other modules.

Char Signedness

Do not assume that plain char is either signed or unsigned. Only use plain char objects to hold text. Do not peform arithmetic on such objects (except perhaps subtraction), and do not use them for indexing into arrays. If necessary, declare such objects with explicit sign, or use casts.

Structure Packing

Structure packing can be a source of interesting problems. To avoid most of them, keep in mind the following recommendations.

For structures that are only used internally within a single project, it is highly recommended to use compiler default packing. This is especially important on platforms where misaligned accesses incur significant penalties (that includes nearly all modern processor architectures) or raise processor exceptions.

However, for structures that describe external data stored in files, received over network links etc., proper alignment is ''critical''. In ideal case, structures are designed to be naturally aligned and structure packing becomes irrelevant. In reality, many structures aren’t naturally aligned (especially those designed on non-RISC architectures or by incompetent programmers) and proper packing must be specified. Problems caused by improper structure packing are notoriously difficult to diagnose.

There is unfortunately no standardized way to specify structure packing. However, the

#pragma pack( push, x )
...
#pragma pack( pop )

form is recommended, as it is supported by many compilers.

Enumeration Sizes

Do not assume that enumeration variables have specific size. They might be int sized or they might not be. If you need to store enumeration values in a structure with externally imposed layout, use a type with explicitly specifed width for storage.

Endianness Issues

Write code that does not depend on ordering of bytes within words when they are stored in memory. If the ordering is significant, provide two variants of the code. Use the __BIG_ENDIAN__ macro to test for big endian targets. Be especially careful with unions and bitfields, as their layout will be quietly altered when endianness changes.

When a sequence of bytes is stored in memory (a text string, for instance), keep in mind that reading the storage as words or doublewords is going to have different results on the 'other' platform.

Filesystem Naming Conventions

Don’t get inventive with file names. Stick to alphanumeric characters, preferably all lowercase, with no spaces or other funny characters. When writing code that needs to parse pathnames, keep in mind that the path separator may be a forward slash, a backslash, or (on Win32 or OS/2) either. Use the __UNIX__ macro to test if UNIX naming conventions should be followed.

File Creation and Opening

Make sure that files are opened with proper mode. While UNIX may not distinguish between text and binary files, many other platforms do. Explicitly open binary files as binary and text files as text.

On the other hand, when using POSIX style I/O, do not assume that the system does not care about access flags. While Win32 may not have user/group/other access flags, many other platforms do. When creating files, make sure appropriate access rights are used.

Clone this wiki locally