Cross-platform C SDK logo

Cross-platform C SDK

Use of C

❮ Back
Next ❯
This page has been automatically translated using the Google Translate API services. We are working on improving texts. Thank you for your understanding and patience.

Most programming languages contain good parts and bad parts. I discovered that I could be a better programmer by using only the good parts and avoiding the bad parts. After all, how can you build something good out of bad parts? Douglas Crockford - JavaScript: The Good Parts.


Programming fast, reducing the probability of error, ensuring portability and generating optimized binaries have been the main purposes of NAppGUI since its inception and that includes a revision of the C language itself. A subset> has been used as a base ANSI-C90 with fixed-size integers <stdint.h>, a feature introduced in C99. We recommend that applications based on this SDK follow the same philosophy. Going into more detail, the objectives pursued have been these:

  • Maximum portability: Even on already outdated compilers like MSVC 8.0 (Visual Studio 2005) or GCC 4.2 (Xcode 3). The latest language features may not be available on platforms where you must port your code (think embedded devices). You also ensure that such code will be compatible with future versions of major compilers.
  • Focus attention: On the "what" and not on the "how". There are times when we make the simple complicated just to justify the use of that new "cool" feature. It is also possible that you are a "hip" addict, which will force you to "modernize" the code to adapt it to a new version of the standard. Focus on solving the problem at hand and, if you can, spend more time on lowering the asymptotic complexity of your solution. NAppGUI will make sure that your applications work wherever they are needed.
  • Avoid irrelevant features: Like C11's multi-threading support (<threads.h>). This is solved with system calls. See Threads.
  • Fast compilation: Certain C constructs are nothing more than a kind of "portable assembler", which the compiler can interpret and translate incredibly efficiently.
  • Small and fast binaries: Derived from the previous one, the generated code will require few assembly statements and will be very easy for the compiler to optimize.

Evidently, this is not the place to learn C nor is it our intention. The core of the language is small and easy to remember, but programming well requires years of practice. What we will do here is show the minimum expression of the language that we use daily. In short, these are our standards.


1. Basic types

  • Void: void.
  • Boolean: bool_t. 8-bit type with only two possible values ​​TRUE (1) and FALSE (0).
  • Integers: uint8_t, uint16_t, uint32_t, uint64_t, int8_t, int16_t, int32_t, int64_t. Fixed-size integers were introduced in C99 by <stdint.h>. We consider it an advantage to know that our variables will have the same size in all systems. The use of int, long, short or unsigned is prohibited, with the sole exception of the comparison functions .
  •  
    
    static int i_cmp_cir(const Cir2Dd *cir1, const Cir2Dd *cir2)
    {
        return (cir1->r < cir2->r) ? 1 : -1;
    }
    
    arrst_sort(circles, i_cmp_cir, Cir2Dd);
    
  • Floating point: real32_t, real64_t. float and double are not used for consistency with integer types.
  • Character: char_t (8 bits). The UTF8 representation is used "de facto" throughout the SDK, so random access to elements of a string is prohibited, since it is a variable-length encoding. Functions included in Unicode or Strings must be used to manipulate arrays of characters. The types wchar_t, char16_t, char32_t are not used (or recommended). However, if you have wide-char strings you will need to convert them to UTF8 before using them in any NAppGUI functions.
  • Using UTF8 strings
     
    
    /* Error! */
    const char_t *mystr = "Ramón tiene un camión";
    while (mystr[i] != '\0')
    {
        if (mystr[i] == 'ó')
        {
            /* Do something */
        }
        else
        {
            i += 1;
        }
    }
    
    /* Correct! */
    const char_t *it = mystr;
    uint32_t cp = unicode_to_u32(it, ekUTF8);
    while (cp != '\0')
    {
        if (cp == 'ó')
        {
            /* Do something */
        }
        else
        {
            it = unicode_next(it, ekUTF8);
            cp = unicode_to_u32(it, ekUTF8);
        }
    }
    
    /* Avoid using wchar_t constants (when possible).
       wchar_t uses UTF16 encoding */
    const wchar_t *mywstr = L"Ramón tiene un camión";
    char_t mystr[512];
    
    unicode_convers((const char_t*)mywstr, mystr, ekUTF16, ekUTF8, sizeof(mystr));
    
    /* This is a NAppGUI function (UTF8-Encoding) */
    label_text(label, mystr);
    
  • Enumerated: Their main task is to manage the specialization and they will be evaluated exclusively within a switch. It is forbidden to assign random values ​​to the elements of an enum, except 1 to the first one. Consider 0 as not initialized and ENUM_MAX(align_t) as invalid.
  • Defining enumerated types
     
    
    typedef enum _align_t
    {
        ekTOP = 1,
        ekBOTTOM,
        ekLEFT,
        ekRIGHT
    } align_t;
    

2. Structures and unions

Definition of structures and unions
 
typedef struct _layout_t Layout;
typedef union _attr_t Attr;

struct _layout_t
{
    Cell *parent;
    Panel *panel;
    bool_t is_row_major_tab;
    ArrSt(Cell) *cells;
    ArrPt(Cell) *cells_dim[2];
    real32_t dim_margin[2];
    color_t bgcolor;
    color_t skcolor;
};

union _attr_t
{
    struct _bool_
    {
        bool_t def;
    } boolt;

    struct _int_
    {
        int64_t def;
        int64_t min;
        int64_t max;
        int64_t incr;
        String *format;
    } intt;

    struct _real32_
    {
        real32_t def;
        real32_t min;
        real32_t max;
        real32_t prec;
        real32_t incr;
        uint32_t dec;
        String *format;
    } real32t;
};

In general, structure definitions will not be public and will remain hidden in the *.c. This means that automatic variables cannot be declared in the Stack Segment and will only be accessible by functions that accept opaque dynamic objects.

Use of opaque pointers
 
Layout *layout = layout_create(2, 2);
layout_edit(layout, edit, 0, 0);
layout_label(layout, label, 0, 1);
...
panel_layout(panel, layout);
 
/* Layout definition is hidden
   We do not know the content of Layout */
Layout layout;  /* Compiler error! */

Normally, all dynamic objects will have a destroy function. If it does not exist, it is because said object only makes sense as part of another object. For example, there is no layout_destroy() or panel_destroy(), but there is window_destroy which will destroy the entire hierarchy of panels and associated layouts to the window.


3. Control

  • if/else. They always open a {...} block, unless ALL paths consist of a single statement. Using functions as arguments to if/else is generally avoided with the exception of pure functions.
  • Use of if/else
     
    
    if (x == 1)
        i_do_something(j);
    else
        i_do_nothing();
    
    if (x == 1)
    {
        j += 2;
        i_do_something(j);
    }
    else
    {
        i_do_nothing();
    }
    
    if (bmath_sqrtf(sqlen) < 20.5f)
        i_do_something(j);
    
  • while. Nothing to comment.
  • do/while. Not allowed. Use for or while.
  • for. For infinite loops, use for(;;) instead of while(TRUE), as it avoids warnings in some compilers. Since there are ANSI-C based compilers, such as MSVC++ 8.0, we do not use variable declarations inside the for(), a feature that was introduced in C99.
  • Use of for
     
    
    /* Infinite loop */
    for(;;)
    {
        ...
    }
    
    /* Will not work in some compilers (not used) */
    for (uint32_t i = 0; i < 1024; ++i)
    {
        ...
    }
    
    /* Ok */
    uint32_t i = 0;
    ...
    for (i = 0; i < 1024; ++i)
    {
        ...
    }
    
  • switch. It is only used to discriminate between the values ​​of an enum. Any other data type will NEVER be evaluated in a switch nor will an enum be discriminated within an if/else construct. The compiler can drastically optimize the performance of a build with these features.
  • Use of switch
     
    
    switch(align) {
    case ekTOP:
        ...
        break;
    
    case ekBOTTOM:
        ...
        break;
    
    case ekLEFT:
        ...
        break;
    
    case ekRIGHT:
        ...
        break;
    
    cassert_default();
    }
    

4. Functions

  • A function can return nothing (void), a basic type, or a pointer.
  • Input parameters are always const even if they are simple types passed by value.
  • Any input parameter that is not of basic type will be passed by pointer. Never a structure by value.
  • For the output parameters, pointers will always be used. In C there are no references.
  • Parameters in functions.
     
    
    uint32_t myfunc(const uint32_t input1, const Layout *input2, V2Df *output1, real32_t *output2);
    
  • The number of public functions should be kept to a minimum, which will be declared in the *.h and defined in the *.c.
  • Supporting (or private) functions will be defined static, inside the *.c module and will have no declaration.
  • Public function.
     
    
    /* layout.h */
    void layout_hsize(Layout *layout, const uint32_t col, const real32_t wid);
    
    /* layout.c */
    void layout_hsize(Layout *layout, const uint32_t col, const real32_t wid)
    {
        i_LineDim *dim = NULL;
        cassert_no_null(layout);
        cassert_msg(wid >= 0.f, "Column 'width' must be positive.");
        dim = arrst_get(layout->lines_dim[0], col, i_LineDim);
        cassert_no_null(dim);
        dim->forced_size = wid;
    }
    
    Private function. It can only be called inside layout.c.
     
    
    /* layout.c */
    static Cell *i_get_cell(Layout *lay, const uint32_t c, const uint32_t r)
    {
        register uint32_t position = UINT32_MAX;
        cassert_no_null(lay);
        cassert(c < arrst_size(lay->lines_dim[0], i_LineDim));
        cassert(r < arrst_size(lay->lines_dim[1], i_LineDim));
        position = r * arrst_size(lay->lines_dim[0], i_LineDim) + c;
        return arrst_get(lay->cells, position, Cell);
    }
    

5. Scopes

Variables are declared at the beginning of a block and cannot be mixed with statements, unless we open a new scope. Declarations mixed with statements is a C++ feature added to the C99 standard, but not all C compilers support it. Yes, it is allowed to initialize a variable by calling a function.

Variable scopes in C
 
{
    /* Ok! */
    uint32_t var1 = 5;
    uint32_t var2 = i_get_value(stm);
    uint32_t var3 = i_get_value(stm);

    i_add_values(var1, var2, var3);

    /* Error in C90 compilers */
    uint32_t var4 = 6;

    /* Ok! */
    {
        uint32_t var4 = 6;
        ....
    }
}

6. Pointers

Apart from the advantages of using pointer arithmetic when implementing certain algorithms, in NAppGUI pointers are used essentially in two situations:

Special mention should be made of the function pointers that are widely used in C, but less so in C++ as the language hides them inside vtables. However, a strategically placed function pointer can make it easier for us to add specialized functionality to existing objects, without having to adopt a more purist object-oriented design.

Listing 1: Use of function pointers.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
typedef struct _shape_t Shape;
typedef void(*FPtr_draw)(const Shape*, DCtx *ctx);

struct _shape_t
{
    ArrSt(V2Df) *points;
    Material *material;
    ...
    FPtr_draw func_draw;
};

static void i_draw_conceptual(const Shape *shape, DCtx *ctx)
{
    /* Do simple drawing */
}

static void i_draw_realistic(const Shape *shape, DCtx *ctx)
{
    /* Do complex drawing */
}

Shape *shape[N];
Shape *shape[0] = heap_new(Shape);
Shape *shape[1] = heap_new(Shape);
shape[0]->func_draw = i_draw_conceptual;
shape[1]->func_draw = i_draw_realistic;
...

for (i = 0; i < N; ++i)
    shape[i]->func_draw(shape[i], ctx);

7. Preprocessor

Our standards make heavy use of the preprocessor, especially for type checking at compile time. This helps to detect errors in the code before running the program (static analysis), as opposed to the C++ RTTI that does it once it is running (dynamic analysis).

Using the preprocessor to check types.
 
#define arrst_destroy(array, func_remove, type)\
    ((void)((array) == (ArrSt(type)**)(array)),\
    FUNC_CHECK_REMOVE(func_remove, type),\
    array_destroy_imp((Array**)(array), (FPtr_remove)func_remove, (const char_t*)(ARRST#type)))

ArrSt(Product) *products = arrst_create(Product);
...
static void i_remove_product(Product *product)
{

}
...

/* 'products' and 'i_remove_product' will be checked at compile time */
arrst_destroy(&products, i_remove_product, Product);
Dynamic typing is not necessarily good. You get static errors at runtime, which really should be catchable at compile time. Rob Pike.

8. Comments

In general, the use of comments will be reduced as much as possible. A comment will be placed at the beginning of each file as a general description. We also use a comment line as a separator when implementing functions.

stream.c
 
/* Data streams. Manage connection-oriented communication */

#include "stream.h"
#include "stream.inl"
#include "bfile.h"
#include "bmem.h"
...

/*---------------------------------------------------------------------------*/

static void i_func1(void)
{
    /* Do something */
}

/*---------------------------------------------------------------------------*/

static void i_func2(void)
{
    /* Do something */
}
C++ comments // Comment... are NOT allowed, as they generate warnings in certain gcc -std=gnu90 compilers.

Another aspect that is totally prohibited is the inclusion of documentation blocks within the source code, not even in the headers themselves. NAppGUI uses ndoc for documentation tasks, a utility that allows you to create html/pdf documents enriched with images, cross-references, examples, etc. and that uses its own files totally separated from the code. Another added advantage is the cleanliness of the *.h headers of all the modules, where it is very easy to locate what we are looking for.

Documentation blocks are NOT allowed.
 
/* Forbidden, non used */
/*! Gets the area of ​​the polygon.
    \param pol The polygon.
    \return The area.
*/
real32_t pol2d_areaf(const Pol2Dd *pol);
Header example in NAppGUI.
 
/* 2d convex polygon */

#include "geom2d.hxx"

__EXTERN_C

Pol2Df* pol2d_createf(const V2Df *points, const uint32_t n);

Pol2Df* pol2d_copyf(const Pol2Df *pol);

void pol2d_destroyf(Pol2Df **pol);

void pol2d_transformf(Pol2Df *pol, const T2Df *t2d);

const V2Df *pol2d_pointsf(const Pol2Df *pol);

uint32_t pol2d_nf(const Pol2Df *pol);

real32_t pol2d_areaf(const Pol2Df *pol);

bool_t pol2d_ccwf(const Pol2Df *pol);

bool_t pol2d_convexf(const Pol2Df *pol);

__END_C
All comments in NAppGUI are made in English language.

9. Input/Output

Input/output is not part of the C language as such. As the language spread in the mid-1970s, a number of useful routines were grouped together into what became the Standard C Library. NAppGUI encapsulates all its functionality in Sewer, Osbs or Core generally implementing it as much more direct and efficient calls to the operating system.

Use of safe I/O functions.
 
/* Do not use cstdlib in applications */
#include <stdio.h>
FILE *fp = fopen("/tmp/test.txt", "w+");
fprintf(fp, "This is testing for fprintf...\n");
fclose(fp);

/* Use NAppGUI functions */
#include "stream.h"
Stream *stm = stm_to_file("/tmp/test.txt", NULL);
stm_printf(stm, "This is testing for stm_printf...\n");
stm_close(&stm);
Use of the Standard C Library is not recommended. Look for the equivalent function in Sewer, Osbs, or Core.

10. Mathematical algorithms

NAppGUI uses C++ templates to implement any function or mathematical algorithm. With this it is possible to offer float and double versions in an elegant way and with easy maintenance. The templates are hidden and not exposed in the API, so that their use remains ANSI-C90 compliant. For more information Math templates.

NAppGUI makes internal use of C++98 template<> to implement everything related to mathematical calculation.
❮ Back
Next ❯