C language

From ScienceZero
Jump to: navigation, search

There are countless ways of tripping yourself up using the C language. The compiler will happily accept what you write, no matter how stupid the mistake is. This document points out some of the most common things that you need to know.

The stack

The stack has a limited space allocated to it, all your variables and temporary storage used by function calls are stored on the stack. When the limited space runs out you will not get a warning, your program will simply fail. You need to know how to adjust the size of the stack so it fits the requirements of your program.

Symptoms:

  • The program counter seems to jump around at random
  • Corrupted variables and data
  • Interrupt functions fail

The heap

The heap is a data structure that is used to keep track of free and allocated memory. If the heap is not initialized any attempts at reserving memory with malloc will fail.

Symptoms:

  • Crashes when you try to access memory

How the compiler sees numbers

The compiler will interpret any number with what is an american style decimal point as a double and all other number as an int. You can force a different type by adding a letter or combination of letters after the number.

100    - int
100U   - unsigned int
100L   - long
100.2  - double
100.2L - long double
100.2F - float
0100   - octal
0x100  - hexadecimal

Preprocessor

Preprocessor commands are identified by the # at the beginning of the line.

#define

Define a named preprocessor constant, the preprocessor will replace <name> with <value>.

#define <name> <value>
#define xy x+y         // This may cause hard to find bugs
#define xy (x+y)       // do this instead

#include <filename>

#include tells the preprocessor to open a file the file and insert it at the point of the command at compile time. If the file name is surrounded by angle brackets < > the compiler will search for the file in a region designated by the operating system as SET INCLUDE. If the file name been is surrounded by double quotes the compiler will only search the default directory for the file.

Escape characters

Escape characters must be used inside double quotes for strings and single quotes for characters.

\a bell character
\b backspace
\f form feed
\n new line
\r carriage return
\v vertical tab
\t horizontal tab
\? question mark
\\ back slash
\’ single quote
\” double quote
\ooo octal number
\xxx hexadecimal number

Declarations

volatile

The volatile keyword tells the compiler that the following object is subject to sudden change in a way that is not described in the source code. For example a RS-232 data register that receive data from the outside. Volatile forces the compiler to generate code that access the memory location each time instead of cache it in a register. Can be applied to any declaration.

If you pass a volatile pointer to a function, volatile will be stripped from the pointer so always use volatile at the point of memory access.

Symptoms of missing volatile statements:

  • Code fails when you enable compiler optimizations
  • Code fails when interrupts or DMA/Hardware peripherials are enabled
I2C1_CR1 (*((volatile unsigned long *) 0x40005400))

extern

The extern keyword indicates that the actual storage and initial value of a variable, or body of a function, is defined elsewhere, usually in a separate source code module.

extern int counter;

static

The static keyword tells the compiler to preserve the last value of the variable between successive calls to that function. All static class variables are initialized to 0 when they are created.

static int counter;

const

Read only. Can be applied to any declaration.

const volatile unsigned long int base_address = 0xFFFF;

register

Tells the compiler to force a variable to stay in a register for improved performance. The compiler can ignore the keyword and use some other form of optimisation.

register unsigned int i;

Pointers

Using a pointer that returns 32 bits from pointed location
Declare a pointer
unsigned int *pointer;
Set a pointers address
pointer=(unsigned int *)address;
Read a pointers address
address=(unsigned int)pointer;


Using a pointer that returns 8 bits from pointed location
Declare a pointer
unsigned char *pointer;
Set a pointers address
pointer=(unsigned char *)address;
Read a pointers address
address=(unsigned char)pointer;


Read data at pointed to address:

data=*pointer;


Set data at pointed to address:

*pointer=data;


Read address of a variable:

address=&variable;


Using pointers in subroutines
In the .h file
void MyFunction1(unsigned int *);
void MyFunction2(unsigned int []);	//This allows a 32-bit 1 dimensional array of any size to be passed to a function
void MyFunction3(unsigned int [][8]);	//for multi-dimensional arrays, the size of the final axis must be given
In the .c file
void MyFunction1(unsigned int *register)
{
  *register |= 1;
}

void MyFunction2(unsigned int arrayname[])
{
  unsigned int var;
  var=arrayname[0];
}

void MyFunction3(unsigned int arrayname[][8])
{
  unsigned int var;
  var=arrayname[0][0];
}
void MyFunction4(unsigned int (*arrayname)[8]) //more preferable way
{
  unsigned int var;
  var=arrayname[0][0];
}
Usage
unsigned int OneDimensionArrayName[8];
unsigned int OneDimensionArrayName[8][8];

MyFunction1((unsigned int *)&RegisterName);
MyFunction2(OneDimensionArrayName);
MyFunction3(TwoDimensionArrayName);


Pointer subtraction

The following statements apply to all pointers in C. They also apply to pointers, other than pointers to members, in C++:

  • When one pointer is subtracted from another, the difference is obtained as if by the expression: ((int)a - (int)b) / (int)sizeof(type pointed to)
  • If the pointers point to objects whose size is one, two, or four bytes, the natural alignment of the object ensures that the division is exact, provided the objects are not packed.
  • For packed or longer types, such as double and struct, both pointers must point to elements of the same array.

Control flow

for

  • for (initialise; test; modify) { statement } ;

do

do { statement } while (expression) ;

while

while (expression) { statement } ;

Switch

switch(exspression)
{
	case constant-expression :
	//code
	break;
	default: // This is taken if no case statements match.
}

break

Pass control to the statement after the loop

continue

Pass control to the start of the loop

If

  • if (expression) { statement }
  • if (expression) { statement } else { statement }
  • if (expression) { statement } else if (expression) { statement } else { statement }

? (ternary condition)

Is this wise? It seems like more pain and very little gain...

  • (expression1) ? (expression2) : (expression3)
z = (a > b) ? a : b;
   is the same as
if (a > b) z = a; else z = b;

goto

  • goto label

Artithmetic and bitwise logical operations

Arithmetic operations

+ Addition
- Aubtraction
* Multiplication
/ Division
% Modulus (remainder after an integer division)

Bitwise logical operations

  • & AND
  • | OR
  • ^ EOR
  • ~ NOT

Shifts (ARM compilers)

  • Right shifts (>>) on signed quantities are arithmetic (implementation defined).
  • Any quantity that specifies the amount of a shift is treated as an unsigned 8-bit value.
  • Any value to be shifted is treated as a 32-bit value.
  • Left shifts (<<) of more than 31 give a result of zero.
  • Right shifts of more than 31 give a result of zero from a shift of an unsigned value or positive signed value. They yield –1 from a shift of a negative signed value.


  • >> - Right shift
  • << - Left shift
  • >>=
  • <<=
  • Rotate
    • Rotate right (ROR) for unsigned int can be implemented as r0 = ((r0 >> n) | (r0 << ((sizeof(unsigned int) * 8) - n)));
    • Rotate left (ROL) for unsigned int can be implemented as r0 = ((r0 << n) | (r0 >> ((sizeof(unsigned int) * 8) - n)));

Relational and logical operators

In the evaluation of long logical expressions, the program starts on the left side of the expression and evaluates the expression until it knows whether the whole expression is true or false, and it then exits the evaluation and returns a proper value.

Relational

>  greater than
<  less than
>= greater than or equal to
<= less than or equal to

Logical

&& will return TRUE if both of its operands are TRUE
|| will return TRUE if either of its operands are TRUE

Equality

== is equal to
!= is not equal to

Arrays

An array is a collection of objects that are stored inconsecutive memory locations. An array is designated at declaration time by appending a pair of square brackets to the array name. If the size of the array is to be determined at the time of declaration, the square brackets can contain the number of elements in the array. Arrays are often allocated on the stack so make sure you size the stack to fit the arrays and variables. C does not have proper strings, instead an array of chars terminated by 0 is used.

extern int a[];
long rd[100];
float temperatures[1000];
char st[] = {“Make a character array”};
float pressure[] = {1.1, 2.3, 3.9, 3.7, 2.5, 1.5, 0.4};
int counter[64] = {0}; // This will fill each cell with 0
int counter[64] = {1}; // This will fill the first cell with 1, the remaining 63 cells with 0


If the array is made constant by using the const keyword the compiler can store the data in flash memory. This may cause slower access time but saves on stack space since the array doesn't have to be copied to RAM.

const unsigned char font[] = {0x00,0xff,0x81,0x81,0x81,0x81,0xff,0x00};

Type specifiers

Data types (ARM C and C++) (ARM32)

Name        Size in bits
char         8 (signed or unsigned is implementation defined so always specify) 
short       16
int         32
long        32
long long   64
float       32
double      64
long double 64
pointer     32
bool (C++)  32

Memory alignement in bits is word length or 32 whichever is smallest. The low word of a long long is at the low address in little-endian mode, and at the high address in big-endian mode. Internal SRAM is often little endian independendt of the endian mode of the CPU.

Defining and using custom types (structures)

to define:

struct pixelType
{
	int R;
	int G;
	int B;
};

to use:

struct pixelType screen[10][10];

int main(void)
{
	screen[2][2].R=80;
}

Enumerations

enum MEM_FLAGS {

 MEM_CCM        = 1,
 MEM_SRAM1      = 2,
 MEM_SRAM2      = 4

};

Defining bitfields

If insufficient space remains, whether a bit-field that does not fit is put into the next unit or overlaps adjacent units is implementation-defined. The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined. The alignment of the addressable storage unit is unspecified.

struct pixelType
{
	unsigned int R : 1;
	unsigned int G : 16;
	unsigned int B : 32;
};

This will store R as 1 bit, G as 16 bits, and B as 32 bits. These will be packed into unsigned ints (4 bytes each) and take up 8 bytes total.
The rule for packing is, if the next variable's length does not fit in whole, into the current word, then it will be placed into the next word.
pixelType in this case will take up 8 bytes, and look like this:

31                                 0
 00000000 0000000G GGGGGGGG GGGGGGGR
 BBBBBBBB BBBBBBBB BBBBBBBB BBBBBBBB

However, if it is defined as:

struct pixelType
{
	unsigned int R : 16;
	unsigned int G : 32;
	unsigned int B : 1;
};

Then it will take up 12 bytes, and look like this:

31                                 0
 00000000 00000000 RRRRRRRR RRRRRRRR
 GGGGGGGG GGGGGGGG GGGGGGGG GGGGGGGG
 00000000 00000000 00000000 0000000B