Benutzer-Werkzeuge

Webseiten-Werkzeuge


becki:linux:c

Dies ist eine alte Version des Dokuments!


C Tips

A Proposal for unified Exception Handling

Assertions

Concepts are borrowed mainly from Java here.

Use assert to handle errors in code. An assertion can be seen as a runtime exception which is never catched and can be switched of. Both assertions an runtime exceptions deal with errors in the code.

Use check when you neigher want or can afford exception handling nor program termniation :?:

/* assert.h */
#define check(expr) (__check((expr), #expr, __FILE__, __LINE__))
int __check(int expVal, const char *expStr, const char *file, unsigned line);
 
/* assert.c */
int __check(int exp, const char *expS, const char *file, unsigned int line) {
    if (!exp) fprintf(
        stderr,
        "\n!!!Check failed:\n  expression: %s\n  file: %s\n  line: %u\n",
        expS, file, line
    );
    return exp;
}

Handle exceptions with the tips below:

Error Handling in Functions

For error handling its acceptable to use goto. Thus the indention level isn't increased by error handling:

int addConfigValue(const char *key, int value) {
    int err= 0; // always initalize because of function of type fopen()!
    FILE file;
 
    if (! file= fopen("values.conf")) { /* exemplary function call */
        err= FIRST_ERR;
        goto do_cleanup;
    }
 
    value += getValFromKey(key, &err); /* exemplary function call */
    if (err) {
        err= SECOND_ERR;
        goto do_cleanup;
    }
 
    if (err= setValFromKey(key, value);) { /* exemplary function call */
        err= THIRD_ERR;
        goto do_cleanup; /* not really necesarry but saver if something is added later */
    }
 
    do_cleanup:
    /* clean up: close files, etc and print debug message */
    return err;
}

Error Reporting in Functions

This rules only apply if the function is stand-alone i.e. it is not an instance method of a class.

If possible use always int as error variable. If the function…

  • doesn't need to return anything, use int as return value as error indicator
    • 0 means ok
    • positive values mean an error happend, the return value is the error code
  • returns a pointer, than returning null indicates error (like fopen() and malloc()). To get the exact reason for the error a pointer to int as error indicator can passed as argument.
  • returns only positive values or 0 (e.g. an array index) than negative values indicate error
  • return anything else, a pointer to int as error indicator can passed as argument

Error Reporting in Classes or Librarys

FIXME Needs reworking

Error Member Variable in Classes

If the function is an instance method of a class, the error code should be written to an instance variable of type int (eg this→err), so that the return value remains alwas free for other informations. To get an integrated error handling in the class, all instance methods shall use this mechanism rather than many different mechanisms mentioned above :!:

FIXME Verify: Keep in mind that all public methods eighter have to set this→err to 0 or check this→err at first, because otherwise the method may return falsely with an err code which did not happen at all!

Proposal: Favor resetting over checking this→err because: — 2007-10-23

  • There is less code and runtime overhead
  • Client code is responsible for correct err handling like in standard C

Error Message Array

Have a central place for error codes and error texts. Public functions must use these error codes to indicate errors for client code:

/* json.h */
enum Json_ErrorCodes {
    JSONERR_OK,
    JSONERR_WRONG_TYPE,
    JSONERR_CREATESTRING,
    JSONERR_FROM_LIB_A, /* An undocumented error (errmsg is missing) */
    JSONERR_FROM_LIB_B, /* An undocumented error (errmsg is missing) */
    JSONERR_ERRCNT,     /* To get the number of error codes */
};
 
/* json.c */
static const char *errmsgs[]= {
    NULL,
    "Element %u is not a JsonValue of type %s!",
    "String creation failed!",
    NULL,
    NULL
}
/* #define ERRMSG_CNT (sizeof(errmsgs)/sizeof(errmsgs[0])) obsolete use errcnt */

FIXME Becaus of inheritance count of error codes and size of error messages must match!

During development, when the centralized array with the error messages is still missing, the error reporter function falls back to just printing a default message like:

Json ERROR: json.c 33: Code 4
        at ...

Error Types

We distinguish between primary errors and follow-up errors.

A primary error typically results from a failed logical test, eg. if the value of a variable isn't within a certain range. The function always aborts after a primary error. All primary errors must be reported.

A follow-up error results from a failed call to a function of the same or another class. The function may abort after a follow-up error or handle that error (see below). But if the function aborts, it must be reported.

Primary errors are reported with an error text, the file name and the line number where they occured. The centralized error codes are always used. Errors may reported directly where they occor or centralized at the end of the function (see above).

If a follow-up error is not handled (i.e. function aborts), it will also be reported, but indented. Only file name and line number is printed. Example:

ArrayList ERROR: Index out of range!  <== primary error
        at arrayList.c:69             <== follow-up errors
        at json.c:137
        at json.c:267

Follow-up Errors

Error code

If a function must be aborted because of a follow-up error, the error code of the called function will not be returned. Instead one error code of the own lib or class, is returned (exception translation). That way the error codes of different classes and libs are independent from each other. Typically every used class get only one error code in the client class.

This also means that a class normally has more error codes than error messages. If this is the case, the error message array has to be filled with NULL-Pointers where messages are missing. This is necessary because with subclass usage (see below) the size of the array must be the same as the error message count.

If the follwow-up error results from a call to a function of same class or lib, the set error code is not touched. It code is only set once, the error text is only reported once, directly where the true error happens. The callers of the function where the error occured just report the line number and file of the funciton call.

Delegate Error Report to called Function

This is the default. A follow up is reported as described above (indendet, with filename and line number, but without error message). The error message comes from the called function.

Handle

DELETEME If a function of a client class C does not abort after a follow-up error e.g. library class L it handles these error. In this case the error reporting of L mutst be switched of completly.

But this also means, that all follow up errors of L must be eighter handled or reported as primary error in C

To switch off error reporting of class L, L must provide a function for that, like L_setVerbosity(). In the body of this function all error reporting in classes that L on his part uses, must also switched of.

Sublasses

If class B is derived form class A, than b also uses the Diagnose member of class A insted of creatin an own. Class B does this by adding it's custom error messages to the Diagnose member of class A.

Summary

  1. FIXME

Further Proposals and Ideas

dependend (nested) errors

Suppress the output of deeper nested primary errors as soon as less deeper nested code calls itself the primary error reporter function Diag_err()

For example if an item is searched in an container by the help of the method of the container class, the error messages of the container are irrelevant. It schould just be reported that the item wasn't found in the client code, not in the container class. The analogy in Java would be a catched exception which isn't reported neither.

This could be done by adding all error messages to a char array. Each call to the report function of a primary error empties that array. The array must be secured against overflow (a growable container is too expensive) and reporting the content of the array needs an extra function call (at the end of main() :?:).

FIXME Think about: Make an error class which contains an int code, a char[80] message and a pointer to the previous error object. No stack, no heap necessary??? Problem: all objects must still exist, when err chain is reported — 2008-01-10

Loops

Loops and break

int i= 0;
while (i<MAX) {
    if ( someThingHappend() )     break;
    else { doSomething(); i++; }
}

is the same as:

int i;
for (i=0; i<MAX; i++) {
    if ( someThingHappend() )     break;
    else doSomething();
}

While and For

for (expr1; expr2; expr3)
    statement

ist according to „The C programming language“ p.59 the same as:

expr1;
while (expr2) {
    statement
    expr3  //expr3 comes after statement
}

CLI args and main()

int main(int argc, char **argv){
    char * progName= argv[0]; // Filename of programm itself
    if (argc >= 2) char * firstArg= argv[1]; // First Argument
}
 
int main(void){}
// is also possible (Carm p.416)
// don't omit void! (Carm p.278)

Position of Pointer Sign

Some investigations:

int main(int argc, char* argv[]) // Stroustrup p.126
int main(int argc, char* argv[]) // Josuttis p.21
int main(int argc, char* argv[], char *env[]) // (!) Breymann p.216
int main(int argc, char *argv[]) // glibc reference
int main(int argc, char *argv[]) // Kernighan / Ritchie
int main(int argc, char *argv[]) // Harbison / Steele
int main(int argc, char **argv)  // gtk reference
int main(int argc, char **argv)  // qt reference
int main(int argc, char **argv)  // fluxbox source

→ I use

char *cp

because

  1. it seems to be more common
  2. declarations like „int a, *ap;“ are possible

Standard In- and Output

stdin

char lin[MAX_LINE_LEN];
while (fgets(lin, MAX_LINE_LEN, stdin)) {/* do some thing */}
/* Reads stdin line by line into the array lin */

stdout, stderr

printf("%s=%u\n", key, value); // writing to stdout
fprintf(stderr, "%s=%u\n", key, value);  // writing to stderr

Colorizing stdout

Just an example:

printf("\033[32mgreen text\033[0m");
printf("\033[1;31mbold red text\033[0m");

See color_indicator[] in sourcecode of ls, ABS Guide and C&R S.38

Constants

const

const int ic = 6;
/* int const ic = 6; Seems to be the same (gcc 3.4.6 / 2007-10-25) */
 
ic = 7; //invalid
ic++;   //invalid

see CaRM p.81

const and pointer

const int * pointer2const; // pointer to constant data
int * const constPointer; // constant pointer (points always 2 the same place)
const int * const constPointer2const; // constant pointer to constant data

see CaRM p.81 and source code of CVS

const and array of pointers

Rule: An array type is always automatically converted to a constant pointer to the first element

int a, b;
const int * const api[] = {&a, &b};
const int * const * const api = {&a, &b};
*(api[0])   = 9;  // forbidden by FIRST const in array definition
  api[0][0] = 9;  // forbidden by FIRST const in array definition
  api[0]    = &b; // forbidden by SECOND const in array definition

Tested with gcc 3.3.4

const and (array of) function pointers

int if1(void) {return 1;}
int if2(void) {return 2;}
void pointer2FunctionTest(void) {
    int (*       fp1)(void) = if1; // fp1,fp2: pointer to function returning
    int (* const fp2)(void) = if2; // int and empty parameter list
    fp1 = if2; // valid
    fp2 = if1; // invalid: ok
}
void arrayOfPointers2FunctionsTest(void) {
    int (*        fa[])(void) = {if1, if2};
    int (* const cfa[])(void) = {if1, if2};
    fa[0]  = if2; // valid, but intended?
    cfa[0] = if2; // invalid because of const: ok
}

Note the position of the const variable, this is the only position of const that
makes sense (Tested with gcc 3.3.4 and CadulCompiler for Rmos)

const in function parameters

void fkt(const int a, const int *bp, int * const cp) {
    a++;      // invalid!
    bp++;     // valid
    (*bp)++;  // invalid!
    cp++;     // invalid!
    (*cp)++;  // valid
}

pointer to constant data (bp) is imho the only useful & reasonable use

function returning const

int if1(void) {return 1;}
const int cif1(void) {return 1;}
void functionReturningConstTest(void) {
    int i; const int ci;
    i  = cif1();     i  = if1();     // both valid
    ci = cif1();     ci = if1();     // both INVALID!
    i  = (cif1())++; i  = (if1())++; // both INVALID!
    i  = ++(cif1()); i  = ++(if1()); // both INVALID!
}

→ function returning const is imho useless

function returning const pointer

int * ipf(void) {return 0;}
const int * cipf(void) {return 0;} // funct. returning pointer 2 constat data
void functionReturningConstPointerTest(void) {
    int *ip;  const int *cip;
    ip= ipf();   // ok
    ip= cipf();  // generates warning (gcc): imho better would be an error!
    cip= ipf();  // ok
    cip= cipf(); // ok
}

Definition of constant Strings

Getting started

[static] const char mystring[]= "String" // right, needs 6 Bytes
[static] const char *mystring=  "String" // false, needs 6 Bytes + Pointer
  • static to limit the scope for global strings. Not necessary inside a code block.
  • see also de.comp.lang.c FAQ → Frage 2.2 / K&R p.101 / Carm p.124

Detailed Description

/** Array of constant data.
    Is automatically converted to a CONSTANT of type "pointer to const char"
    (the type of the array-elements) wich contains the address of the first
    element. See CARM p 95. Best way to initalize strings.
    */
const char a[]= "Alma";
 
/** Constant pointer to constant data, but not a CONSTANT (like a above)
    Intitalizing a string like this is bad, see above
    */
const char * const b= "Berta";
 
/** Same type as b, but ok for a re-use of a e.g. as function argument. */
const char * const c= a;
 
/** Array of constant pointers (second const) to constant data (first const)
    1st const necessary because of a */
const char * const apc[] = {
    a, "Willi", /* perfectly legal */
    /* b, c     // illegal, because extern or static variables can only be
                // initialized with CONSTANTS not constant variables
                // see [[Initialisation of variables]] */
};
 
/** Constant Pointer (3rd const) to constant pointer (second const)
    to constant data (first const) */
const char * const * const ppc= apc;
 
int main(void) {
    /* a        =  0; //err: Every array is conv. to const ptr to 1st element */
    /* b[0]     ='A'; //err: a[] is declared as const */
    /* apc[0]   =  0; //err: because of 2nd const in apc definition */
    /* apc[0][0]='d'; //err: because of 1st const in apc definition */
    /* ppc      =  0; //err: because of 3rd const in ppc definition */
    /* ppc[0]   =  0; //err: because of 2nd const in ppc definition */
    /* ppc[0][0]='d'; //err: because of 1st const in ppc definition */
    return 0;
}

sizeof

//Feld von Zeigern auf Funktionen:
static const unsigned char (*setS5Bit[])(unsigned char zielNr, unsigned char bit)={
    NULL,//Reserviert, wenn Eingang nicht aktiv
    konvKom0SetS5Bit,
    konvKom1SetS5Bit,
    extStmSetS5Bit,
    extBtmSetS5Bit,
    extGeraetSetS5Bit
};
#define GETS5BITADR_FKTANZ (sizeof (setS5Bit) / sizeof (setS5Bit[0]))

Bestimmung der Größe des obigen Arrays:
Einsicht in die vom Preprocessor erzeuge Zwischendatei peripher.i (mit der Compileroption -VCPP zeigte, daß GETS5BITADR_FKTANZ wirklich nur textuell durch die sizeof- Klammerausdrücke ersetzt wird, und keine Berechnung erfolgt.
Einsicht in das generierte Assemblerfile peripher.asm ergab, daß die sizeof-Operatoren zur Compilierungszeit und nicht zur Laufzeit ausgeführt werden.
→ Keine Verschwendung von Laufzeit.

Initialisation of Variables

/* Extern & static variables must be initialized with a constant expression:
    See C&R 4.9 p.83
    */
int a; /* uninitialized extern or static variable defaults to 0 */
int b=1;
const int c=3;
//int m= b; /* error in c! (no error in c++) */
//int n= c; /* also error in c! (no error in c++) */
 
/* automatic variables need no constant expression: See C&R 4.9 p.83 */
int main() {
    int u= b+c; /* valid shortcut for: int u; u=b+c; */
    int v[]= {a,b}; /* valid in c with gcc & in c++ */
    return 0;
}

Note: The initialisation of the (stack-)array v is valid with gcc, but issues a warning when gcc is called with the -pedantic switch.

Cadul Compiler Deficiency

int anInt;
struct Point {int x; int y;};
struct Point aPoint= {2,3};
int main(){
    int o= anInt;
    int p[]= {anInt,anInt};
    struct Point q={3,4};
    struct Point r[]={{3,4},{5,6}};
    struct Point s=aPoint;
    struct Point t[]={aPoint,aPoint}; /* error with Cadul! (no error with gcc) */
}

Transfer of Array Sizes

/* arraySource.c: */
int arr[]= {3, 2, 1};
const unsigned ARR_CNT= sizeof arr / sizeof arr[0];
/* arraySource.h: */
extern const unsigned ARR_CNT;
/* target.c: */
#include <stdio.h>
#include <stdlib.h>
#include "arraySource.h"
 
int main(int argc, char **argv) {
    int size= atoi(argv[1]);
 
    static int staticCmdArr[size];      /* Always Error! */
    int         stackCmdArr[size];      /* works with gcc without -pedantic-error */
 
    static int staticTransArr[ARR_CNT]; /* Always Error! */
    int         stackTransArr[ARR_CNT]; /* works with gcc without -pedantic-error */
 
    return 0;
}
#Compiler Commands:
gcc -Wall -ansi -pedantic-error -c target.c && gcc -c arraySource.c && gcc target.o arraySource.o && a.out 7
gcc -Wall                       -c target.c && gcc -c arraySource.c && gcc target.o arraySource.o && a.out 7

FIXME No satisfying solution so far — 2006-08-28 12:02

static

static int eumel= 0;

Outside of a fuction (global Variable)

To prevent linker problems when other object files use other global variables with the same name.

Inside a fuction

  • The Variable is actually created in the data segment of the programm & not on the stack.
  • The value of the variable remains from one call to the next call of the function
  • :?: static should not be necessary for const variables in functions; the compiler is probably clever enough to put constant values to the proper places in memory.

How to select Integer Variable Types

  1. Always use int (also when int is way too big or the value will never be never negative, eg. counter variable in for loops), unless one or more of the following exceptions apply:
  2. FIXME Test this: Use unsigned int as parameter type in function declarations when only positive values are allowed. This saves you from the overhead of testing for nonnegative values.
  3. Use unsigned types when bit operations are performed (unsigned types with proper size recommended)
  4. Use another, smaller type when space is relevant (eg in struct definitions or large arrays)
  5. Use a bigger type when the space in int is not sufficient

Unsorted Tips

  • Buffer for strings: Define a constant for the maximal stringlen eg: #define MAXFOOLEN 80 and reserve memory with one byte more for the 0-terminator: char sbf[MAXFOOLEN+1];
  • :?: Test: Include header files which are necessary for the interface in foo.h. Include header files necessary only for the implementation in the foo.c. The dependencies for the makefile is the sum of both.
  • Passing 0-termintad arrays saves from defining and additionally passing the array size. This is useful for arrays of any type, not just char[].
  • Design constructors to expect only absolutely essential information. Move optional parameters to other setup functions for the class.

2do / Pending

Cookies helfen bei der Bereitstellung von Inhalten. Diese Website verwendet Cookies. Mit der Nutzung der Website erklären Sie sich damit einverstanden, dass Cookies auf Ihrem Computer gespeichert werden. Außerdem bestätigen Sie, dass Sie unsere Datenschutzerklärung gelesen und verstanden haben. Wenn Sie nicht einverstanden sind, verlassen Sie die Website. Weitere Information
becki/linux/c.1259244436.txt.gz · Zuletzt geändert: 2009-11-26 14:07 von becki

Impressum - Datenschutzerklärung