Tags:
create new tag
view all tags

The Coding Standard for eSDO

Author: Elizabeth Auden
Version: 1.0
Date: 30 January 2006
Details: Derived from the C Coding Standard for Envisat ICE project v1.0, 17/6/96.

Acronyms and Abbreviations

ANSI American National Standards Institute
MSSL Mullard Space Science Laboratory
RAM Random Access Memory
CVS Concurrent Versions System
TBD To Be Decided
TBC To Be Confirmed
UCL University College London

Introduction

Goals of a Coding Standard

Coding standards support the goals of software engineering. Specifically, we are concerned with creating robust, easy-to-use, maintainable systems which are compliant with user requirements. The goals are as follows:

  • To allow us to generate robust, bug-free code from the outset, in the shortest possible time.
  • To allow us to produce our source code in a standard format that is easy to understand and therefore to maintain.
  • To encourage coding techniques which scale well into larger applications.
  • To produce code which is easy to test.

Definitions

Projects are classified as follows:

  • Class A projects: Those projects undergoing a full software engineering cycle, normally for external customers. This standard shall apply in full. ENVISAT Ice Algorithms is a Class A project.
  • Class B projects: Everything else. Use the standard as appropriate, but always keep to the spirit of it. This document defines two levels of conformity with the coding standard.
  • Mandatory practises. These practises must be followed, without exception, in all class A projects. Mandatory guidelines are recognised by use of the underlined word shall.
  • Recommended practises. These are strongly recommended practises. They are recognised by use of the underlined word should.

Further guidelines are given in this standard. These include warnings about problems commonly experienced. Text which is neither mandatory or recommended is given for information only and there is no requirement for guidelines to be followed.

Scope

This coding standard shall apply to all code specifically written for the eSDO project: solar algorithms, visualization tools, and other support code. It does not apply to legacy code inherited for solar algorithms, nor does it apply AstroGrid modules used within deployment. Standards for wrapping C code as IDL modules will be addressed in summer 2007 when it becomes appropriate to deploy algorithms as SolarSoft modules.

Document Overview

This remainder of this document discusses standards for the C language. There is a general introduction, naming conventions, guidelines on declarations and the use of coding constructs. This is followed by a section on the layout and documentation of code. Naming conventions are given for variables, constants, macros, functions, procedures, and files. There follows a section dealing with variables; their declaration, initialisation, and other issues. Functions are discussed with reference to prototyping, returning status indicators, and handling error conditions. Safe practises (and pitfalls) are documented in the section on coding constructs. Qualities such as clarity, readability, and maintainability are the focus of the sections on code layout. Conventions are given for documenting code by means of comments and headers. Coding style is addressed here. The C pre-processor is discussed along with tools which can be used in developing C code. References are given at the end of the document.

Standards Relating to C Code

All C code within the remit of this project shall be written in ANSI C, with the following exception:

  • Inherited pre-ANSI C code vital to the operation of solar algorithms.

Naming Conventions

We now define some conventions on variable and function naming. The motivation for this is that we wish to maximise clarity in our code. We use different layouts for variables, functions, constants and macros, so that one can immediately resolve the various elements in code. In addition, we use prefixes on variable, function and constant names, so that we can infer type without constantly referring back to the declarations. This of course implies that we should take care to use the names correctly.

Variables

Meaningful variable names shall be used at all times. Each variable must have a name that represents its role. For example, a velocity variable is called velocity_of_entity. The goal is to maintain clarity and maintainability. This means that the meaning of a piece of code must be clear to reviewers, customers and subsequent generations of software engineers inheriting your code.

Names should be unambiguous. For internal symbols it can be assumed that up to 31 characters are significant. The ANSI standard states that at least the first 6 characters of the name are significant for external symbols (e.g. libraries). One should take care to ensure unique identifiers. Similar names should be avoided. This prevents ambiguity and ensures that a mis-typed variable name does not erroneously identify another variable. Words in a variable name shall be separated using underscores, and we shall not use capitals.

Functions

Function names shall be meaningful, giving an indication of the functionality provided. Care must be taken to avoid re-using the names of C library functions. The number of significant characters is compiler dependent. In order to make function names stand out from variables, we use capitalisation to differentiate words in a name, rather than the underscores used in variable names. Headers or comments shall make clear whether a routine is of global or local scope.

Examples of valid function names and their declarations include:

char RoutineName();
long int DateAndTime();
short int GetDataStructure();
void InitialiseVariables();

Constants

Constants which must be hardwired into code should be defined using the preprocessor directive #define. For constants set up by the pre-processor using #define we shall always use upper case identifiers. Numerical constants shall be enclosed in round brackets, to prevent ambiguities during substitution. Comments shall accompany constant definitions. Use #defined constants rather than hardwired numbers in the code whenever this makes sense.

Dynamic data shall be loaded from files at run-time. Static constants which could foreseeably change may also be handled using this method.

Some examples of valid constant definitions are given below :

#define VELOCITY_OF_LIGHT (2.99e08) /* Velocity of light */
#define WINDOW_REFERENCE_NO (3) /* Window Number */
#define MY_NAME FRED JONES /* Programmer Name */

It is possible to include an explicit cast within the text for substitution. For example:

#define D_VELOCITY_OF_LIGHT ((double) 2.99e08)

This technique should be used wherever ambiguities may occur. Floating point constants should be cast to double and integer constants should be cast to long int, to force a calculation to be performed at maximum precision.

Macros

For Macros we shall use upper case, and prefix the name by M_ thus:

#define M_DEBUG(var, format) printf(#var " = %" #format "\n", var)

For a discussion of macros see Tizzard (1992), Maguire (1993), and McConnell (1993).

File Names

  • C source code files shall have the extension .c.
  • Header files shall have the extension .h.
  • C library files shall have the extension .a.

Variables

The use of variables local to routines is preferable to the indiscriminate use of global variables. However in C, it is possible to go beyond this and to define local variables with scope limited to a compound statement. This has the effect of optimising code, but should be avoided as it introduces complexity into a module. If temporary variables are used, special care needs to be taken to avoid duplicating the names of variables with wider scope, otherwise difficult-to-spot bugs may be introduced. The use of global variables should be avoided, unless the situation genuinely requires an entity to be global. In most cases, it is better to define typed data structures which are passed to properly prototyped functions. Liberal and arbitrary use of globals is usually symptomatic of poor program design.

Declarations should be aligned using tabs for readability as follows:

  • shortint my_short; /* Loop variable */
  • long int my_lon_array[20]; /* Array of longitude values */
  • double my_double; /* Temporary variable */
  • fred_struct my_fred_struct; /* Data about Fred */

Order

We shall declare no more than 1 variable per line. Variables shall be ordered by type.

  • void
  • char
  • short int
  • long int
  • float
  • double
  • union
  • struct

other pointers to, register and unsigned variants should precede the normal types in each class. For example:

  • register short int inner_loop; /* Loop variable */
  • unsigned short int iteration_counter; /* Loop variable */
  • short int max_num_iterations; /* Maximum iterations */
  • short int num_frequencies; /* Number of frequencies */
  • short int outer_loop; /* Loop variable */
  • float * pointer_to_float; /* Float Pointer */
  • float frequency_interval; /* Interval in frequency */
  • float input_data[20]; /* Array of user input */

Hence we are able to organise our declarations for easy reference during development and maintenance activities.

Initialisation

Variables shall be initialised either in the declaration, or immediately after. This prevents uninitialised variables causing bugs. For example:

  • short int gate_index=10;
or

  • /* declarations */
  • short int gate_index;
  • /* more declarations */
  • gate_index=10;

It is often useful to initialise unused variables with a 'recognisable' value, if possible outside the legal range of the variable, (e.g. -999, -99.999) so that when dumping variables during debugging, these values will show up as not being used. Setting an initial value of zero can cause confusion, as it is usually a legal value. In certain situations, one may consider using the extrema values available in the system file <limits.h>. Undefined values for each type may be defined by the pre-processor directive #define.

Ambiguity of Variable Size

The generic type int should not be used in coding. When using UNIX routines that expect or return type int , an explicit cast should be used. In this way we remain conscious at all times of variable size. An exception to this is when the returned int is used only by UNIX routines, or compared with known values. An example is when an open statement returns a file descriptor. This should be of type int as we never explicitly reference this. The size of char, int, short int and long int etc. can vary according to the machine architecture. In most applications this is not important, and can be an advantage in that one can code without considering the architecture in great detail. However, certain quantities may overflow (for example, a 2-byte int), and especially in real- or quasi-real-time applications, or when manipulating telemetry, the explicit size must be used.

It is good practice to employ the sizeof() operator to check the size of variables in code that may be run on multiple platforms. At the very least, a warning can be given that the code may produce wrong results.

Functions

Prototypes

Function prototypes shall always be used. Global function prototypes shall be placed in one or more header files. Prototypes for static functions (local to a specific file) may be included in either a header file or within the source code file. Function prototyping ensures that only legal entities can be passed between a function and its caller. In rare cases when we want to pass, say a short int to a function that is expecting a variable of type fred, based on a short int then we must use an explicit typecast. Thus, passing out-of-type variables becomes a conscious action that must be seriously considered on each occasion.

Typed Functions

Function returns shall be typed. Functions that do not return a value shall be declared void. The naming convention discussed in section 2.2 Functions shall be used. The main routine shall be typed (either as being of type void or integer):

  • void main();

Functions which return a pointer, the type of which is unknown in advance should be declared of type 'void *', i.e. a generic pointer. It is good practice to use a (void) typecast when calling functions that do not return a value. Then it can be seen that no return was anticipated, and has not been overlooked.

Error Codes

Functions shall return an error or status code. For functions of type short int or long int the status code shall be 0 to indicate success or a positive integer to indicate failure. For functions returning a pointer, a valid pointer shall indicate success or a NULL pointer to indicate failure. Note that pointers should always be checked to ensure they are non-NULL before they are dereferenced. Almost all the UNIX input / output and file management system routines return error codes, either as a formal function return value, or in the parameter returns. Many library routines also return error and status information. All error and status codes shall be examined and appropriate action taken. This includes status and errors from custom functions and library functions. The minimal action is to report an error if a non-nominal condition is detected.

These error returns and status codes are a rich source of information. If we take the minimal line above and simply report non-nominal conditions, then we achieve four things:

  1. We rapidly isolate malfunctions due to coding or specification errors.
  2. We trap unexpected conditions which need to be handled, thus finding a solution quickly.
  3. We trap more problems before we ship our code.
  4. The code is easier to maintain post-delivery.

As an example of the second case above, the error return in question might simply be a 'device full' message which warns of the failure of a write operation. A good piece of code should be robust against such a condition, but the function is easily overlooked in the design. If the condition is not trapped and handled, then strange behaviour may be encountered later on, far from where the real problem is. By checking the error return, we find out about the shortcoming quickly, without getting into debugging, and we rectify the design error efficiently. Consider a 'file open' code fragment as follows:

#include <stdio.h>
#include <errno.h>
char message[80];
...
other declarations and code
...
/* Open the binary file for read */
out_file=fopen(output_file, "rb");
if(out_file==NULL)
{
printf("Open file failed, file= %s , ", output_file);
printf("called from vMyRoutine \n");
sprintf(message, Error no: %5i ", errno);
perror(message);
exit(code); /* Abort to limit damage and save time */
} /* End of minimum functionality error handler */

The error information is minimal. Failure is indicated by a null pointer returned. errno and perror() should be utilised to identify the nature of the problem. We also indicate what happened and where using printf statements. A more sophisticated handler might attempt to recover from certain errors in appropriate cases.

Data Types

Type definitions should be used as appropriate to define data types for variables, structures, and pointers. Type definitions should be contained within a header file. Using typedef is a powerful technique for avoiding bugs associated with variable types. Data types should not be mixed in a single expression or statement.

Coding Constructs

We now set out some rules and guidelines relating to common coding constructs. Our aim is clarity, robustness, and freedom from bugs. Although C is a 'structured' language, it has many features which allow violation of the structured methodology. Gotos shall not be used. To control loops, if clauses, and case statements, one should use while constructs, and continue and break statements. Be warned that continue and break introduce complexity to logic that can be hard to follow. There is no substitute for excellent design.

Loops

In compound statements associated with loops, a comment shall follow the closing bracket, indicating what operation is being closed. This is particularly important for nested loops. It is important to get nested for loops the right way round for efficient code. C stores two-dimensional arrays in row-major order (the opposite way to FORTRAN which does it column-wise). Hence the inner 'for' should process the rows. In the following example, the rightmost index should increment first.

/* Sum all the elements of the array */
for (control_index=1; control_index<10; control_index++)
{
    for (inner_index=1; inner_index<10; inner_index++)
    {
        sum_of_array=array[control_index][inner_index];
    } /* end of inner loop */
} /* end of outer loop */

If Constructs

Avoid compound logicals if possible. It is easy to generate a flawed logic chain that works some of the time, but may fail obscurely. Since if-else logic is very fast, be generous, and set it out so that meaning is obvious. Be careful to avoid the classic "= instead of " bug. One should avoid mixing the assignment operator (=) with the logical equal operator () in statements. An ANSI compatible version of lint will report errors of this type. Avoid dangling elses - else is dangerous unless used appropriately. Consider the following if-else constructs:

if(<condition>)
    if(<condition>)
        <action>;
else <action>;

It is much safer and clearer to use:

if(<condition1>)
{
    if(<condition2>)
    {
        <action>;
        <action>;
    } /* end of if condition2 */
    else <action>;
} /* end of if (<condition1>) */

When if logic becomes complex, consider breaking up the logic into a chain of if statements, or using a switch (case) statement.

Case (Switch)

Consider the following case clause. A common bug is to omit one of the break statements, causing unplanned behaviour. However, sometimes one may wish to omit the break. If so, such a condition should be explicitly commented. Better still, the code should be redesigned to avoid the situation, perhaps by using functions in the case statement. Remember, we can be generous with code in most cases. case :

case <condition>
    <action>;
    break;
case <condition>:
    <action>;
    /* Break deliberately omitted */
case <condition>:
    <action>;
    break;

Operator Precedence

Since there are 15 layers of precedence in the operator set of C, it is easy to introduce errors if one tries to rely on memory when combining operators in expressions. While it is useful to learn the precedence rules, it is also good to adopt a style which makes the intended operation clear, both to the programmer and to the compiler. Parentheses should be used to avoid ambiguity and to aid readability. Complexity is also avoided by the use of functions and reducing complex statements to a sequence of shorter statements.

Code Layout

When code is written by more than one person, style can vary markedly, which destroys readability. If we standardise our layout, it means that all information will be included in every routine, and it will all be in the same place. In addition, we should be able to follow code constructs more easily, if the same methods are used by everyone. Hence we combine our naming convention with our layout convention. We define the layout for the code header as well as the code itself.

Clarity

Clarity is one of the primary goals of modern software engineering. There is no need, for general purposes, to use obscure or RAM saving techniques. Generally, a function will be fast enough with a straightforward implementation. We may consider functions which must be fast or compact as part of a separate engineering subphase. Often the straightforward implementation is retained as a system integration and debugging aid. The oft quoted rule is "Don’t optimise, but if you must optimise, do it later".

To this end, beware of computer algorithm books from the 60’s and 70’s. Consider all the available solutions. The most appropriate for your application may be simpler and easier to understand, exploiting the abundant resources now available. We shall be considering how we can develop standard coding methods which produce efficient, tight code, without destroying readability.

Style

We will always need to print out the code that we produce, and include it in deliverable documentation. For this reason, we should write our code such that the line length does not exceed 80 characters, with a hard at the end. This means that in many cases we will need to split lines (preferably at a clause in the logic). To this end, indents shall be of 4 characters rather than a full 8 character tab. In addition we shall use an appropriate non proportionally-spaced font for printing source code, such that we get 80 columns of text on a portrait orientation A4 page. Code bracketing using the {} characters should be arranged such that the brackets line up vertically. This increases the readability of code greatly.

if (<expression>)
{
    <statement 1>
    <statement 2>
} /* End of if */

Only one statement shall be included on any 1 line.

Module Length

Length of code in a module will vary, but as rule it should be short and well-organized for two reasons. Firstly the understandability of the function of the code is enhanced if it can be taken in at a glance. Secondly short code is easier to test, as there will be fewer paths through the code to consider. We are concerned with writing robust, bug-free code, and as such we must engineer our software with that in mind. This means that only one major logical function should be addressed per routine, and the algorithm should be built up from these fully-tested and robust building blocks. Overall class length need not be restricted, but limit function length (~50 - 75 lines) to keep functionality simple and straightforward.

Comments

We shall adopt the following convention for in-code comments.

/* ------------------------------------------------------------------
Major code section heading or explanatory text
*/

/* -- Minor code section heading --------------------------------- */

var1=statement;    /* Explanatory comment */

Source Code Layout

Source code files shall be organised according to the following template. Normally, constant and macro definitions, typedefs, and prototypes should be placed in include files. System include files should precede project include files. Any pre-processor statements relevant to conditional code shall be positioned at the top of the file. This makes it immediately clear that conditional code is present and in use.

/*-- pre-processor statements for conditional code --*/
/* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
File Header
------------------------------------------------------------------
-
/*-- pre-processor statements --*/
/*-- standard C headers --*/
/*-- programmer created headers --*/
/*-- constant and macro definitions --*/
/*-- type definitions --*/
/*-- global and external variable declarations --*/
/*-- local function prototypes --*/
/*-- functions --*/

Function Layout

Functions shall be organised according to the following template. Normally, there will be one function definition per file. The exception to this is when declaring functions local to a file. Additionally, functions may be logically grouped into files.

/* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
Function Header
------------------------------------------------------------------
-
<type> <function_name>
{
/* local variable declarations */
/* ------------------------------------------------------------------
Major code section heading or explanatory text
*/
/* -- Minor Code Section Heading --------------------------------- */
var1 = (short int) <expression>; /* Explanatory Comment */
} /* End of <function_name> */

Headers

Code headers are delimited by use of a '+' and '-' character in the first column of the header field. This facilitates easy extraction of header text for documentation purposes.

File Header Template

A file header provides general information on the code present in each file. This includes the file name and an SCCS block. The routines in the file should also be listed to aid the reader.

/* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
System: eSDO
Sub-system: <name>
<description of functionality provided by this file>
----------------------------------------
Filename : %M%
CVS Version Number : %I%
Last Update : %G%
What string : %W%
----------------------------------------
Routines:
<routinenames>
----------------------------------------
Copyright eSDO
------------------------------------------------------------------
-
*/

Function Header Template

A function header shall be included for each function. We include the function declaration within the header for clarity. All global variables and constants used by a function shall be documented in the function header in the order of their declaration. This ensures that any globals which are used or modified by the routine are properly documented. Functions called and called by (except for service routines) shall be listed. The UNIX utility cflow can be used to verify the correctness of the calling tree. System routines should not be listed.

/* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
<type> <routineName>( <type> <argument>, <type> <argument>, ....)
Purpose of routine: <description>
---------------------------------------------
Documentation cross reference
<ADD Process Identifier>
<other documentation>
---------------------------------------------
Coding by:
<author> Created: <date> Version 1.0
<author> Modification: <date> <version> <description>
Reviewed by
<reviewer> <date> <version> <description>
Test by:
<tester> <date> <version> <description>
--------------------------------------------
Inputs <argument>: <description> of argument 1
limits on argument 1
Outputs: <argument>: <description> of argument n
limits on argument n
Return: <description>
limits on return
---------------------------------------------
Calling tree: Called by : <routine name>
Calls : <routine name>
---------------------------------------------
Global Variable And Constants Documentation
Global variables used:
g_variable name <description> <where defined>
Global constants used
CONSTANT NAME <description> <where defined>
-----------------------------------------------------------------
-
*/

--++ Pre-Processor

The pre-processor performs actions prior to the compilation process. This includes replacement of defined identifiers by pieces of text, inclusion of conditional code, and inclusion of other source files. Tizzard (1992) addresses pre-processor pitfalls. We can employ the pre-processor as a powerful debugging tool. We can insert conditional code into routines. By this we mean code that is only compiled if certain constants are defined by #define. As a simple example of this, consider the following function in a module file:

#define DEBUG
long int DoSomething(long int input, long int output)
/*
Header text
*/
{
/* Declarations */
<actions>;
#ifdef DEBUG
if (value < 20)
{
    printf("end_gate_1 = %d \n", end_gate_one);
    printf("start_gate_2 = %d \n", start_gate_two);
    printf("end_gate_2 = %d \n", end_gate_two);
    printf("start_gate_4 = %d \n", start_gate_four);
    printf("end_gate_4 = %d \n", end_gate_four);
    printf("start_gate_5 = %d \n", start_gate_five);
} /* End of if value < 20 */
#endif
}

This pre-processor construct allows us to embed debugging code into our source. Note that because it is conditional at compile time, it can be left in the delivered source code without affecting the functionality or the size of the delivered product. All that is required is to remove the #define DEBUG statement at the beginning of the file header. This can be accomplished by maintaining two versions of an include file, and so configuration control can be maintained. Diagnostic code should be left in place where it is judged to be useful for maintenance activities. For example, code verifying the correct execution of a module is useful for maintenance, but code placed to find a bug is not useful once the bug is corrected.

In working with scientific data, the input data may be corrupt, and although every effort should be made to handle all eventualities cleanly and correctly, certain situations may be overlooked. These conditions can be trapped by using conditional code to trap 'impossible' conditions (i.e. conditions that if they occur, must be bugs). This can be done as above with C code, but a more elegant method is to use assertions.

Assertions

Assertions shall be used appropriately in this project. There is a macro called assert, which resides in the standard header assert.h. Assert packages debug statements of the type discussed above into a single line statement. It allows us to write code such as:

{
<declarations>
assert(value > 20 && sum != 100);
/* Code to be protected */
statement1;
statement2;
}

If the argument of the assert macro evaluates to false, the assertion will trigger, and program execution will be aborted. The assertion generates a message such as:

Assertion failed: value > 20 && sum != 100
File yourfile.c, line nnn

The assert can be disabled on the SUN system by use of #define NDEBUG in the file pre-processor definitions. The number of bugs trapped in this way during development can be quite unnerving, but it is better to trap them than having them crop up in delivered code. If there are problems with delivered code, the assertions are still there to assist post delivery debugging. NB one must not use assertions to handle error conditions which should be properly handled by the code. They are strictly to trap conditions that should never arise in the functioning software. Maguire (1993) discusses the use of assertions at length, including guidelines for the customisation of the assert macro. If we decide that we need custom assertions, this shall be considered formally as part of the software engineering design activity, and implemented under full configuration control.

Tools

The UNIX environment provides several tools and utilities which may be useful during development of code.

  • gcc This ANSI C compiler shall be used. Compiler warnings should not be generated for deliverable code. Any such warnings must be justified and the justification documented. Compiler options such as range checking should be turned on during development. Useful compiler options during development are -fd reports old style function declarations and definitions. -v performs stricter semantic checks. -Xc checks that code is strictly conformant to the ANSI standard. Options should aim to produce optimised code for deliverables.
  • splint splint A static analyser for C programs. splint shall be used to scans source code and reports features which are likely to be bugs, non-portable, or wasteful.
  • prof A dynamic profiler. Reports on time spent within each function and the number of calls to each function during program execution. The compiler option -p must be used before profiling. prof should be used to profile stable modules of computationally intensive code.
  • make A utility to compile and link files. Makefiles should be laid out according to a template TBD. Lint can and should be incorporated in makefiles. Files under version control are automatically retrieved by make (specific rules for retrieval can be given).
  • cflow A utility to produce calling trees for C programs. cflow shall be used to generate graphs for finished code.
  • CVS Concurrent Versions System. CVS on msslxx will act as the code repository and version control. New, tested code shall be uploaded to
  • CuTest Package for unit testing C code. Each function shall have at least one test. Suites of function tests for a specific solar algorithm or visualization tool shall be gathered in a single test file. A link to the test file shall be added to one of two "All Tests" (for all solar algorithms or for all visualization tools)files in CVS.

References

  • Curry, David A., "Using C on the UNIX System", O'Reilly, 1989. ESA, "PSS-05-05 Guide to the Software Detailed Design and Production Phase", Issue 1, 1992.
  • McConnell, Steve, "Code Complete", Microsoft, 1993.
  • Maguire, Steve, "Writing Solid Code", Microsoft, 1993.
  • Tizzard, Keith, "C for Professional Programmers", 2nd Ed., 1992.
  • "IDL User's Guide: Interactive Data Language Version 4", Research Systems Inc., 1995.
  • "Product Specification Document", [10676/93/1-HGE] FRa-PSD, 1993.
  • "The C Coding Standard for the WAP QA Contract", v1.2, MSSL, 1996.
  • "The C Coding Standard for the FRAPPE-B Contract", v1.0, MSSL, 1996.
  • "The Climate Physics C Coding Standard", v1.0, MSSL, 1995.
  • “The Envisat ICE C Coding Standard”, v1.0, MSSL, 1996

-- ElizabethAuden - 30 Jan 2006

Edit | Attach | Watch | Print version | History: r4 < r3 < r2 < r1 | Backlinks | Raw View | More topic actions
Topic revision: r4 - 2006-01-31 - ElizabethAuden
 
This site is powered by the TWiki collaboration platform Powered by PerlCopyright © 2008-2019 by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding TWiki? Send feedback