Difference between revisions of "C language"
(→Things to remember about the C language if you want to keep sane) |
(→Pointers) |
||
(82 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
− | + | 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 | + | *http://codepad.org/ |
+ | *https://www.sourcelair.com/ | ||
+ | |||
+ | == 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: | Symptoms: | ||
− | The program counter seems to jump around at random | + | *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 === | ||
+ | Define a named preprocessor constant, the preprocessor will replace <name> with <value>. | ||
+ | <nowiki>#define <name> <value></nowiki> | ||
+ | <nowiki>#define xy x+y // This may cause hard to find bugs</nowiki> | ||
+ | <nowiki>#define xy (x+y) // do this instead</nowiki> | ||
+ | |||
+ | === #include <filename> === | ||
+ | <nowiki>#include</nowiki> 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 === | === volatile === | ||
− | The volatile keyword tells the compiler that the | + | 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: | Symptoms of missing volatile statements: | ||
*Code fails when you enable compiler optimizations | *Code fails when you enable compiler optimizations | ||
*Code fails when interrupts or DMA/Hardware peripherials are enabled | *Code fails when interrupts or DMA/Hardware peripherials are enabled | ||
+ | |||
+ | I2C1_CR1 (*((volatile unsigned long *) 0x40005400)) | ||
=== extern === | === 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 === | === 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 === | === const === | ||
Line 20: | Line 80: | ||
const volatile unsigned long int base_address = 0xFFFF; | 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 == | |
− | + | {| class="wikitable" border="1" cellspacing="0" | |
− | + | |+ '''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; | ||
+ | |} | ||
− | < | + | {| class="wikitable" border="1" cellspacing="0" |
− | + | |+ '''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:'''<br /> | ||
+ | data=*pointer; | ||
+ | |||
+ | |||
+ | '''Set data at pointed to address:'''<br /> | ||
+ | *pointer=data; | ||
+ | |||
+ | |||
+ | '''Read address of a variable:'''<br /> | ||
+ | address=&variable; | ||
+ | |||
+ | |||
+ | {| class="wikitable" border="1" cellspacing="0" | ||
+ | |+ '''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 | + | case constant-expression : |
//code | //code | ||
break; | 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 [[C_language#const|const]] keyword the compiler can store the data in flash memory. This may cause slower access time but saves on [[C_language#The_stack|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 == | == Type specifiers == | ||
− | === Data types === | + | === 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.<br /> | ||
+ | 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.<br /> | ||
+ | pixelType in this case will take up 8 bytes, and look like this:<br /> | ||
+ | 31 0 | ||
+ | 00000000 0000000G GGGGGGGG GGGGGGGR | ||
+ | BBBBBBBB BBBBBBBB BBBBBBBB BBBBBBBB | ||
+ | |||
+ | However, if it is defined as:<br /> | ||
+ | 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:<br /> | ||
+ | 31 0 | ||
+ | 00000000 00000000 RRRRRRRR RRRRRRRR | ||
+ | GGGGGGGG GGGGGGGG GGGGGGGG GGGGGGGG | ||
+ | 00000000 00000000 00000000 0000000B | ||
− | + | [[Category:Computing]] | |
− | + | ||
− | + | ||
− | + |
Latest revision as of 14:24, 26 October 2016
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.
Contents
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
Declare a pointer |
unsigned int *pointer; |
---|---|
Set a pointers address |
pointer=(unsigned int *)address; |
Read a pointers address |
address=(unsigned int)pointer; |
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;
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