It is very likely, and already happened, that code written by external objects programmer will be included in official Max/Fts distribution, and that the FTS team will be required to support this code.
As the introduction of tabpeek
and tabpoke
proved, putting some code
in a directory called "unsupported" don't prevent it to be widely used
if the functionality implemented is needed in the user base.
For this reason, no future release will include unsupported C code written in Ircam; if an object is included in the official Ircam distribution, it is fully supported by the development team.
But adding source code to support to a system already difficult to maintain raises the maintenance cost, and our resources are limited.
In order to reduce this cost, we require that any contribution under the form of C source code have to comply with the following guidelines in order to included in an official distribution.
In general, if your C source do not fully comply with the the guidelines will *not* be accepted, under any condition; for some of the point we use the term "recommended"; this means that we may decide to accept some source C also if they don't comply with some of the recommended guidelines.
Some programmers write the code with the attitude of helping the
compiler to generate faster code; this programmers are wrong.
Most of the time, the assumption made by these programmers refer to a
compiler technology of the 70s; modern compiler can perform low-level
and function-level optimization a lot better than most of the better
assembler programmer; more over, the analysis on the source code is so
deep and complex that most of the programming tricks at the source
level are worthless, and sometime get also get the reverse effect.
On the contrary, writing a clear and readable code help the
programmer, the designer of the system, to find optimization at the
architectural and algorithmic level, that the compiler cannot find.
Nevertheless, sometime you may need to perform hand tuning on the
code; if you do need this, avoid blind optimization based on your
personal feelings; first, write the programs in the most readable way,
and care about low level optimization only when you can prove, that
the performance problem can be solved at this level (most of the time,
it cannot).
In many platforms there are fine analysis tools (
Also, don't try to invent the wheel again and again; for
most of the time consuming tasks, there are well known optimization
techniques; ask around, check with your local vector computation guru,
or try to ask us.
A file referring to a global function should include the proper ".h"
header file, and not having a local copy of the function declaration.
This documentation may be included in the C source or in the header
file as a comment.
The only exception to this rule, for the time being, is
"
Examples:
It must be remembered that the "static" keyword declare the function
name local to the file; this do not means that a pointer to a
static function cannot be used to call the function from an other file.
In the case of an external objects, usually the only function that
should not be static is the "
Note also that a static declaration may in some case allow the compiler
to remove the function body completely, when all the call to the
functions can be inlined.
Example:
On the contrary, assignment that are part of the algorithm in the
strict sense are not allowed, like:
The code should be coded as:
Example:
Every global variable must be properly documented with a comment.
A file referring to a global variable should include the proper ".h"
header file, and not having a local copy of the variable declaration.
Example of good code:
While ANSI C guarantee that static and external variables are initialized to
zero, explicitly initializing them to zero express that the initial value is
part of the semantic of the variable, and document the fact that the zero
value is assumed.
Good Example:
Bad example:
As for functions, r-ember that the "static" keyword declare
that the name of a variable (and not the variable itself) is local
to the current file; the variable (i.e. the data structure itself)
can be still access from other files by means of pointers.
Example:
In no case an assignment can be nested inside a logical expression,
like the following:
Wrong example:
In for, most of the time an assignment in the condition clause is
sign of a badly written for; most of the time the assignment should
be in the iteration clause; anyway, if you really need to put an
assignment in a conditional, we ask you to find the FSF coding
conventions, and add an extra pair of parenthesis, like in:
Goto in C are needed only in very complex situation, to handle the
weakness of C in handling multi-level exit from complex nested loops;
anyway, most of the time such multi-level nested loops are not needed
or a sign of bad programming style; remember, you are not programming
in Assembler any more.
Most of the time a "break" or "continue" statement can substitute the
goto; other time, the duplication of cleanup code, or the introduction
of a clean up function.
The following is an other, quit extreme, example from the Max source
code:
For example, the statement:
Examples:
In general, too many initialization or iterations clauses are
usually a bad programming style indication.
Using commas instead of blocks prevent the use of macros that expand to
blocks. For instance, in the following example (taken from Max/FTS original
sources), defining
A typical example is using the bitwise and operator to implement a
modulo operation with a power of two constant, on the assumption that
the and operator is faster then the modulo (division) operator. What
this argument forgot is that all the modern compiler is able to
automatically perform this kind of optimization; the programmer, as
said at the beginning, should concentrate in writing readable code,
and let the compiler do low level optimizations.
Examples:
Another example taken from the Max is performing a comparison with a
power of two by means of a bitwise and; first, we do not known any
architecture were this would be possibly faster than a standard
comparison, and second, the compiler would find out that by its own.
The code using this kind of tricks is completely unreadable, like in:
Bit operators should be left for real bitmask oriented operations.
An example of this naming convention is the following:
Actually, it is not, and is a very old (70s) C programming convention
coming from the old times where the name space of structure fields was
unique, so all the structure fields defined in the same program had to
be different (so the prefix).
This convention in not used since the late 70s, and all the C
compilers follow the rule that every structure have its field name
spaces, so field name can be reused in different structure definition.
Avoiding this convention will made the code a lot clearer.
In particular, we recommend that you don't use the "x" name inside
object methods to identify the object itself; "x" is not meaningful
as an object name, and it often induce confusion in objects where
a geometrical space is used in the semantic (x coordinates, for examples).
You should use a name clearly referring to the object, like in the classic
object oriented languages, like "self", "this" or "me", for example; we use
the "this" name, following the C++ rules.
A function should be indented like this:
Correct Example:
There should be no #ifdef'ed debug code, unless:
If your editor does not allow you a nice formatting of C code with any
tab size, we suggest you to use a better editor (namely, GNU Emacs).
There are two typical cases we detected in the Max and MaxLib sources:
Another more frequent and dangerous problems have to do with Max
externals: sometimes, and object allocate some buffer at creation
time, and forgot to free them when the object is destroyed.
Some compilers provides a flag to signal calls to non-prototyped
functions. If available, this flag must be set.
This option enable all the possible warning the compiler can issue,
including also a lot of style related warning; actually, many of the
guidelines reported in this document are covered by this compiler
option.
If you don't use gcc, and your compiler do not provide similar feature,
use lint to check your source code.
Authors : Maurizio De Cecco,
François Déchelle
Copyright © 1995 IRCAM.
Some good principles on optimization
The main purpose in writing a program is making it readable to other
human being, including yourself.
pixie
, for example,
on SGI or Dec machines) that allow you to know exactly where FTS spend
time; on the results of these analysis, that are often
counterintuitive, spend your time in optimize only the critical
functions.
Functions
ANSI prototypes
All the functions should be defined and declared using full
ANSI prototypes. For example, this declaration is not allowed:
void foobar();
Assuming that foobar don't take arguments, the correct declaration is:
void foobar(void);
No unused static functions
The code should not include functions that are not called; if they are
debugging function, see the debug code section.
Non local functions
Functions that are not declared static should be correctly prototyped
in ".h" file, included with the sources, and a minimal documentation
should be included on their purpose and arguments.
Return types
Return types for functions should be always specified, i.e.
a function not returning a value should be declared "void",
and a function returning an int should be declared "int".
incrp(int *x)
{
(*x)++;
}
Is illegal because it don't return a value, but is declared implicitly
int; the return type should be declared "void".
addone(int x)
{
return x+1;
}
Must be declared of type int.
Static functions
All the functions that are not intended to be called by name from
other files should be declared static.
Variables
Automatic variable initialization
The Initialization of an automatic variable is allowed in a
declaration only when the initialization is intrinsically part of the
semantic of the variable, i.e. if the variable is part of a complex
data structure, where the consistency is guaranteed by assigning a known
value to the variable.
{
t_atom table_of_values[MAX_VALUES];
int table_fill_pointer = 0;
...
}
is OK, and recommended, because the pair (table_of_values,
table_fill_pointer) together form a data structure that would be
inconsistent without assigning 0 to the table_fill_pointer.
{
int new_size = old_size * GROW_RATE;
int alloc_size = new_size - old_size;
int do_it_carefully = alloc_size > MAX_RECOMMENDED_SIZE:
...
}
This code example (another classic from the MAX original source code)
hide a significant part of the algorithm inside the variable
declarations, making them a lot more difficult to read, and to
comment; moreover, introduce dependency on the order of declarations,
that may introduce errors later.
{
int new_size;
int alloc_size;
int do_it_carefully;
new_size = old_size * GROW_RATE;
alloc_size = new_size - old_size;
do_it_carefully = alloc_size > MAX_RECOMMENDED_SIZE:
...
}
Another case where initialization of automatic variables is not
allowed is in the case of convenience or iteration variables;
iteration variables should be initialized in the context of the loop
(in the for statement or before the while).
{
int i = 0;
for (; i < MAX; i++)
....
}
Is not allowed; the i variable should be initialized in the for statement,
because its value have no meaning outside the loop.
Global variables
In principle, global variables are a "bad idea"; so in general, we
recommend to use no global variables; if they are really needed they
should be defined at the beginning of the file, within a code section
that is explicitly marking as containing global variables definitions.
#include "...."
/* Global variables for memory handling */
/* count how many bytes have been globally allocated */
long fts_byte_counter = 0;
Global variables initialization
If the program assume that a variable (global or local, static or automatic)
have a specific initial value, the variable should be explicitly initialized
to that value.
static int foobar_selected;
static int foobar_counter = 0;
In this case that the initial value of foobar_selected is not used,
and so probably a zero value is meaningless; while the foobar_counter
is significant also at zero.
static int zapn;
void
dozap(void)
{
zapn++;
}
It is not clear from the zapn definition that its initial
value is significant.
No unused constants
There should be no unreferred #define'd constants.
No unused variables
There should be no unused variables in the code, either
local to a function or global to a file.
Register variables
Don't use register declarations; they are useless, because all the modern
compiler ignore the register declarations, and make you think you are
in control of the generated code, while you aren't.
Static variables
All the variables whose purpose and use is local to file
must be declared static.
Control structures
Assignment in `if', `while' and `for' conditions
Is is recommended to don't use assignments in if conditions.
if (file = open(filename, "r"))
{
.....
}
Should be written as
file = open(filename, "r");
if (file)
{
.....
}
The only allowed assignment inside an if condition is at the
expression top level.
int new_size;
........
if ((need_size > old_size)) && ((new_size = old_size * GROW_RATE) < MAX_SIZE))
{
realloc_table(new_size);
}
This example (a classic from the Max original sources) is not readable,
because in the condition we hide an essential part of the algorithm,
that should be coded separately; moreover, the single steps of the
algorithm cannot be properly commented, and the new_size variable get
a scope bigger than the needed.
.....
if (need_size > old_size)
{
int new_size;
/* need reallocation: compute new possible size */
new_size = old_size * GROW_RATE;
/* if not too big, reallocate it */
if (new_size < MAX_SIZE)
realloc_table(new_size);
else
.....
}
About the "while" statement, there are coding styles where the
result is readable, like:
while (c = getchar())
{
... DO SOMETHING WITH c ...
}
Sometimes, a for would do a better job.
while ((c = getchar()))
{
... DO SOMETHING WITH c ...
}
So to mark clearly that it is not a case of a mistyped equality test.
Goto statements
Goto statements are not allowed.
void
fun(...)
{
for (i = 0; i < MAX; i++)
{
.....
if (error())
goto exit;
}
exit:
return;
}
No `for' loops with empty body
There should be no for loops with an empty body.
A for with an empty body hide it's real purpose in the loop
controlling clauses; it should probably be rewritten as while,
to make a lot clearer it's purpose, that programmer look for in the
loop body, and not control.
for (p = list; p->next; p = p->next);
Should be written as:
p = list
while (p->next)
p = p->next;
That put in evidence that the purpose of the loop is to
advance the pointer (the body of the loop), until we don't
have successor (the loop control clause).
Only iteration variable in the `for' clauses
The "for" statement initialization and iteration clause should be used
to modify only the iteration variables, i.e. the variables tested
inside the condition of the for.
for (p = table, i = 0; i < table_size; i++, p++)
.....
Is wrong, because the variable p do not define the loop, but is part
of the algorithm the loop execute; seeing p in the iteration and
initialization condition suggest that the loop is defined in term of
the pointer, while is defined in term of the counter i.
The comma operator
The C comma operator is allowed only inside "for" clauses.
It should not be used as a substitute for a C block.
outlet_int
as a macro instead of a function
leads to a syntax error :
if (x->s_vel || !x->s_sust)
outlet_int(x->s_out2, x->s_vel),
outlet_int(x->s_ob.o_outlet, n);
else
x->s_slink = slink_new(x->s_slink, n);
This should be coded as:
if (x->s_vel || !x->s_sust)
{
outlet_int(x->s_out2, x->s_vel);
outlet_int(x->s_ob.o_outlet, n);
}
else
x->s_slink = slink_new(x->s_slink, n);
Expressions
Bit operators
The use of Bitwise operators used to implement arithmetic operations
is forbidden.
#define SIZE 4096
#define MASK SIZE-1
i = (i + 1) & MASK;
Should be code as
#define SIZE 4096
i = (i + 1) % SIZE;
This coding would work also with a SIZE different from a power of two.
if (size & SIZE_MASK)
The semantic of the test is not known unless you do know the value of
SIZE_MASK.
Pointers and integers
The following is another good example taken from the original Max/FTS code :
sig = (t_sig *)(av[1].a_w.w_long);
Such expressions are guaranteed to be non portable.
Naming
Structure fields names
We recommend to don't follow the naming convention given in the Max
Opcode external object programmers manual about structure fields.
struct foobar
{
int f_foo;
int f_bar;
struct foobar f_next;
};
Structure field name are prefix with a letter corresponding to the
structure name initial and by an "_"; the Opcode documentation report
this as a "UNIX" programming convention.
Variables and functions names
Variable (also local variables and structure fields) and functions
names, should be significant; don't use variables with funny but
meaningless names for your amusement, like "shit", for example
(an other classic in MAX original sources).
Code presentation
Coding indentation style
The Fts team use the vertical indentation style; i.e. any open "{"
or closed "}" is on its own new line.
<return type>
<function-name> ( <args> )
{
<declarations>
<body>
}
All the code supported by this team will be converted to this
indentation style; we recommend, if possible, to use this indentation
style for the submitted sources.
Commented code
There should be no commented code at all.
I.e. nothing like:
x = x + 1;
/* printf("The x value is %d", x); */
Comments on a line
Comments to the right of a code on the same line should comment *only*
that line; comments for blocks or functions or that refer to more than
one line of code should be on one (or more) line by themselves.
new_size = old_size * GROW_RATE; /* compute the new size */
alloc_size = new_size - old_size; /* find the needed new memory */
Wrong example:
if (x > 3) /* If we have more than 3 foobar, frob them */
{
... frob foobar ...
}
This example should be coded either:
/* If we have more than 3 foobar, frob them */
if (x > 3)
{
... frob foobar ...
}
or:
if (x > 3)
{
/* If we have more than 3 foobar, frob them */
... frob foobar ...
}
Debug code
There should be no uncommented "forgotten" debug code.
For example, this is not allowed:
x = x + 1;
#if 0
printf("The x value is %d", x);
#endif
While this is allowed:
x = x + 1;
#ifdef INCREMENT_TEST
printf("The x value is %d", x);
#endif
if and only if the following (or a similar comment) appear at
the head of the file:
/*
Compilation Flags
INCREMENT_TEST enable the code to test the frobar incrementer.
Left here because the frobbar implementation will
soon change and we need to keep the test.
*/
Tab size
If tabs are used in source, they have to be equivalent to 8 chars;
some editor allow you to set a different tab size; this is a bad idea,
because almost any other UNIX tool assume a 8 char tab size.
Memory management
Freeing memory
Care should be taken in assuring that the allocated memory is freed
when not needed anymore.
{
char *p;
p = malloc( ....);
if (error)
return;
}
In this example, some memory is allocated, then an error test is
performed, and if an error situation exist the function return,
without freeing the memory; wile not very likely, it is n example of a
memory leak difficult to catch.
Compilation
ANSI C
The code must be compiled with a full ANSI compliant compiler.
Compilation warnings
You code should generate no warning when compiled with gcc with the
"-Wall" option, or equivalent flags for other compilers, or at least
no warning you know how to fix.
The FTS C Programming Style Guide.