1
0
mirror of https://github.com/stargieg/bacnet-stack synced 2025-10-19 23:25:23 +08:00
bacnet-stack/doc/code-standard.txt
2013-03-21 22:53:31 +01:00

236 lines
9.3 KiB
Plaintext

This software runs on many platforms, and can be compiled with a number of
different compilers; here are some rules for writing code that will work
on multiple platforms.
Regarding tabs, indenting, and code style, we run:
$ indent -kr -nut -nlp -ip4 -cli4 -bfda -nbc -nbbo -c0 -cd0 -cp0 -di0 -l79 filename.c
on the code prior to releasing it. This ensures a standard look and feel
to the code regardless of the authors preferred style. You may certainly
adjust the code to your preferred style using an indent tool. We use the
script indent.sh to adjust all the .c and .h files.
For variable names, separate words within the variables by underscores.
Do not use capital letters as separators. Consider how much harder
IcantReadThis is on the eyes versus I_can_read_this.
Variable and function names are defined with the first words being
descriptive of broad ideas, and later words narrowing down to specifics.
For instance: Universe_Galaxy_System_Planet. Consider the following names:
Timer_0_Data, Timer_0_Overflow, and Timer_0_Capture. This convention
quickly narrows variables to particular segments of the program.
Never assume that a verb must be first, as often seen when naming functions.
Open_Serial_Port and Close_Serial_Port do a much poorer job of grouping
than the better alternative of Serial_Port_Open and Serial_Port_Close.
Don't use C++-style comments (comments beginning with "//" and running
to the end of the line) for modules that are written in C. The module
may run through C rather than C++ compilers, and not all C compilers
support C++-style comments (GCC does, but IBM's C compiler for AIX, for
example, doesn't do so by default). Note: there is an application
called usr/bin/ccmtcnvt in the liwc package that converts the C++
comments to C comments. There is a script utilizing ccmtcnvt called
comment.sh created for this project that searches all the c and h files
for C++ headers and converts them.
Don't initialize variables in their declaration with non-constant
values. Not all compilers support this. E.g. don't use
uint32_t i = somearray[2];
use
uint32_t i;
i = somearray[2];
instead.
Don't use zero-length arrays; not all compilers support them. If an
array would have no members, just leave it out.
Don't declare variables in the middle of executable code; not all C
compilers support that. Variables should be declared at the beginning
of a function or compound statement, or outside a function
Don't use "inline"; not all compilers support it.
Use the C99 stdint.h and stdbool.h definitions for declaring variables
when needed. If they are not defined for your compiler, put those files
into the ports directory for your compiler with the proper definitions.
Sometimes scalable code should just use an int or unsigned declaration.
8-bit unsigned = uint8_t
8-bit signed = int8_t
16-bit unsigned = uint16_t
16-bit signed = int16_t
32-bit unsigned = uint32_t
32-bit signed = int32_t
boolean = bool
Don't use "long" to mean "signed 32-bit integer", and don't use
"unsigned long" to mean "unsigned 32-bit integer"; "long"s are 64 bits
long on many platforms. Use "int32_t" for signed 32-bit integers and use
"uint32_t" for unsigned 32-bit integers.
Don't use "long" to mean "signed 64-bit integer" and don't use "unsigned
long" to mean "unsigned 64-bit integer"; "long"s are 32 bits long on
many other platforms. Don't use "long long" or "unsigned long long",
either, as not all platforms support them; use "int64_t" or "uint64_t",
which need to be defined as the appropriate types for 64-bit signed and
unsigned integers.
Don't use a label without a statement following it. For example,
something such as
if (...) {
...
done:
}
will not work with all compilers - you have to do
if (...) {
...
done:
;
}
with some statements, even if it's a null statement, after the label.
Don't use "bzero()", "bcopy()", or "bcmp()"; instead, use the ANSI C
routines
"memset()" (with zero as the second argument, so that it sets
all the bytes to zero);
"memcpy()" or "memmove()" (note that the first and second
arguments to "memcpy()" are in the reverse order to the
arguments to "bcopy()"; note also that "bcopy()" is typically
guaranteed to work on overlapping memory regions, while
"memcpy()" isn't, so if you may be copying from one region to a
region that overlaps it, use "memmove()", not "memcpy()" - but
"memcpy()" might be faster as a result of not guaranteeing
correct operation on overlapping memory regions);
and "memcmp()" (note that "memcmp()" returns 0, 1, or -1, doing
an ordered comparison, rather than just returning 0 for "equal"
and 1 for "not equal", as "bcmp()" does).
Not all platforms necessarily have "bzero()"/"bcopy()"/"bcmp()", and
those that do might not declare them in the header file on which they're
declared on your platform.
Don't use "index()" or "rindex()"; instead, use the ANSI C equivalents,
"strchr()" and "strrchr()". Not all platforms necessarily have
"index()" or "rindex()", and those that do might not declare them in the
header file on which they're declared on your platform.
Don't fetch data from packets by getting a pointer to data in the
packet, casting that pointer to a pointer to a structure,
and dereferencing that pointer. That pointer won't necessarily be aligned
on the proper boundary, which can cause crashes on some platforms (even
if it doesn't crash on an x86-based PC). This means that you cannot
safely cast it to any data type other than a pointer to "char",
"unsigned char", "uint8_t", or other one-byte data types. You cannot,
for example, safely cast it to a pointer to a structure, and then access
the structure members directly; on some systems, unaligned accesses to
integral data types larger than 1 byte, and floating-point data types,
cause a trap, which will, at best, result in the OS slowly performing an
unaligned access for you, and will, on at least some platforms, cause
the program to be terminated.
The data in a packet is not necessarily in the byte order of
the machine on which this software is running. Make use of
big_endian() which returns non-zero on big_endian machines.
Use "ntohs()", "ntohl()", "htons()", or "htonl()" only in the ports
directories since the header files required to define or declare
them differ between platforms. There are some common functions in
the bacdcode library for converting to and from long and short.
Don't put a comma after the last element of an enum - some compilers may
either warn about it (producing extra noise) or refuse to accept it.
When opening a file with "fopen()", "freopen()", or "fdopen()", if the
file contains ASCII text, use "r", "w", "a", and so on as the open mode
- but if it contains binary data, use "rb", "wb", and so on. On
Windows, if a file is opened in a text mode, writing a byte with the
value of octal 12 (newline) to the file causes two bytes, one with the
value octal 15 (carriage return) and one with the value octal 12, to be
written to the file, and causes bytes with the value octal 15 to be
discarded when reading the file (to translate between C's UNIX-style
lines that end with newline and Windows' DEC-style lines that end with
carriage return/line feed).
In addition, that also means that when opening or creating a binary
file, you must use "open()" (with O_CREAT and possibly O_TRUNC if the
file is to be created if it doesn't exist), and OR in the O_BINARY flag.
That flag is not present on most, if not all, UNIX systems, so you must
also do
#ifndef O_BINARY
#define O_BINARY 0
#endif
to properly define it for UNIX (it's not necessary on UNIX).
Don't use forward declarations of static arrays without a specified size
in a fashion such as this:
static const value_string foo_vals[];
...
static const value_string foo_vals[] = {
{ 0, "Red" },
{ 1, "Green" },
{ 2, "Blue" },
{ 0, NULL }
};
as some compilers will reject the first of those statements. Instead,
initialize the array at the point at which it's first declared, so that
the size is known.
Don't put declarations in the middle of a block; put them before all
code. Not all compilers support declarations in the middle of code,
such as
int i;
i = foo();
int j;
For #define names and enum member names, prefix the names with a tag so
as to avoid collisions with other names - this might be more of an issue
on Windows, as it appears to #define names such as DELETE and
OPTIONAL.
Don't use "variadic macros", such as
#define DBG(format, args...) fprintf(stderr, format, ## args)
as not all C compilers support them. Use macros that take a fixed
number of arguments, such as
#define DBG0(format) fprintf(stderr, format)
#define DBG1(format, arg1) fprintf(stderr, format, arg1)
#define DBG2(format, arg1, arg2) fprintf(stderr, format, arg1, arg2)
...
or something such as
#define DBG(args) printf args
Instead of tmpnam(), use mkstemp(). tmpnam is insecure and should
not be used any more. Note: mkstemp does not accept NULL as a parameter.
Try to write code portably whenever possible, however; note that
there are some routines in the software that are platform-dependent
implementations. The platform independent API is declared in the
header file, and the dependent routine is placed in a ports directory.
Reference: The cross platform aspect of this coding standard is based
on the developer coding standard for Ethereal/Wireshark and has been
modified by Steve Karg for this project. Thank you, Ethereal/Wireshark!