Terry’s C++ Mini-Manual Terry Rogers 3 June 2011 Ver 1.2 18 June 2011 Ver 1.3 28 Oct 2012 – minor updates Ver 1.4 15 May 2014 – significant updates to C Section Ver 1.5 27 May 2014 – Updates to C++ Section This is a handbook for C++ and a summary description only. It does not include Visual C++ per se. See the WROX C++ help file or Microsoft’s MSDN. The first part is just ANSI C which is compatible with C++. <optional> means this part may be excluded. [insert] means replace as shown in text. Underlined sections below are links to parts of this document. ANSI C ANSI C is a union of K&R C and C++ but without OOPS components and having only an update of the older standard library. Table of Contents ANSI C .............................................................................................................................................. 1 Compiler Directives ..................................................................................................................... 4 Statements .................................................................................................................................. 5 Assignment and Operators ......................................................................................................... 5 assignment statement ............................................................................................................ 5 unary operators ...................................................................................................................... 5 binary operators ..................................................................................................................... 6 relational operators ................................................................................................................ 6 Data Objects ................................................................................................................................ 7 declaration: ............................................................................................................................. 7 storage class ............................................................................................................................ 7 Page 1 of 33 type specifiers: primitive type specifiers are: ......................................................................... 8 type modifiers (may be applied to most primitive types) ..................................................... 8 Initialization............................................................................................................................. 8 Arrays ...................................................................................................................................... 9 struct (ures) ............................................................................................................................. 9 Union struct (ures) ................................................................................................................ 10 Enumerated .......................................................................................................................... 12 Procedure and Function Calls ................................................................................................... 13 Function Prototypes.............................................................................................................. 13 Branching and Conditional ........................................................................................................ 14 if/then/else ........................................................................................................................... 14 while --- do while .................................................................................................................. 14 for loop .................................................................................................................................. 14 case statement ...................................................................................................................... 15 Exit from function ................................................................................................................. 15 goto statement ..................................................................................................................... 15 Pointers ..................................................................................................................................... 16 In Line Assembler ...................................................................................................................... 17 Input/Output in Standard Library ............................................................................................. 18 printf/fprintf/sprintf --- formatted output ........................................................................... 18 scanf/fscanf/sscanf --- formatted input................................................................................ 20 fflush(fp);............................................................................................................................... 20 setnbf(fp); ............................................................................................................................. 20 gets/fgets --- get a string....................................................................................................... 21 fopen/freopen --- open a buffered file ................................................................................. 21 fclose --- close a buffered file ret=fclose(fp);........................................................................ 21 clrerr/clearerr --- clear error flag for file............................................................................... 21 Scope of Variables ..................................................................................................................... 22 Subtle Errors Enabled by C Language ....................................................................................... 23 Page 2 of 33 Buffer Over-run ..................................................................................................................... 23 Pointer Addressing ................................................................................................................ 23 Harvard Architecture Problems ............................................................................................ 24 C++ ................................................................................................................................................ 27 Class (Structures) .......................................................................................................................... 27 Simple Parent Class ................................................................................................................... 28 Derived Class ............................................................................................................................. 29 Constructors & Destructors ...................................................................................................... 32 Scope (Public/Restricted/Private)............................................................................................. 32 Embedded SW Problems .......................................................................................................... 32 Operator Overloading ................................................................................................................... 32 Polymorphism ............................................................................................................................... 32 Subtle Errors Enabled by C++ ........................................................................................................ 33 Constructor Chaos..................................................................................................................... 33 Virtual Functions in ROM(!) ...................................................................................................... 33 Page 3 of 33 Compiler Directives syntax: #[directive] where [directive] may be: examples define identifier token-string #define CARRIAGE_RET 0x0D include “filename” or <filename> #include <exec/types> “ “ means local & < > means look in /I library path first. Directives make up a scripting language that includes conditionals and branching. For example, a header file: #ifndef MESSAGES_H #define MESSAGES_H 1 … body of file #endif note ‘#pragma once’ does the same for MSoft compilers Note directives do not end in a semi-colon. Compiler directives constitute their own language in that one can use #if, #else etc. to control code generation for such things as model variances. HOWEVER, it becomes extremely difficult to read through extensive use of such code control forcing you to resort to reading listings. It is recommended that, instead one use directives or better, a versioning system to bring in and delete whole files or at least whole functions and rely on design documentation to link variations. Special debug statements can be put in under control of a conditional (above) and the variable DEBUG or MYDEBUG if the compiler already uses ‘debug.’ The IDE or build file will permit definition of whatever configuration variables, such as DEBUG, that are required. In the current MSoft IDE, one uses different projects in the same Solution to swap out build files and thus predefined configuration variables as compiler directives. Ultimately, compiler directives can be contorted into a macro language which I don’t find desireable since there are no standard validation tools for this approach. Use features of ANSI C++, the IDE and Configuration and Control System instead. There are many more directives. Page 4 of 33 Statements syntax: statement; /* comment */ {statement; statement;...;} /* compound statement */ Statement; // comment to end of line Individual but not compound statements end with a semi-colon. Function code is ultimately contained in one large compound statement which may contain other compound and likely simple statements. Statements define or declare objects, control flow, evaluate expressions, assign values or call functions (subroutines). main() is also a function which is called by the linker supplied start up code. Note that classes and structures also end with a semi-colon as do enumerations. Assignment and Operators assignment statement lvalue op expression /* lvalue means something that can go on the left of = . ) lvalue = expression /* most common */ where: lvalue is an expression referring to a data object e.g. a variable or *p where p is a pointer op is an assignment operator e.g. += lvalue=lvalue + expression -= analogous to above *= /= %= (modulo, operands must not be float) >>= (shift lvalue right by expression bits) <<= (shift lvalue left by expression bits) &= (bitwise AND) ^= (bitwise XOR) |= (bitwise OR) unary operators *expression indirection, meaning the object pointed to and not the pointer itself &lvalue pointer operator, result is pointer to object -expression unary minus !expression logical NOT ~expression one’s compliment of its operand (what size?!?!) ++lvalue numeric ++x is same as x+=1 (but conversion may be required & pointers are different). Increment occurs before use of the resulting value Page 5 of 33 --lvalue lvalue++ same as above but decrementing increment lvalue (type always lvalue). The increment occurs after use of the resulting value lvalue-decrement lvalue (type always lvalue). Same as above but decrementing. (type name) expression cast operator. example: wp = (struct Window *)make_window(args); where wp is type struct *Window (pointer) not required for lvalue conversion sizeof expression size of expression in bytes sizeof(type name) size of [type name] in bytes. This can get you into trouble because you might be talking about “int myarray[20]” and think the sizeof is 20 but in reality, it is 20xsizeof integer in bytes. binary operators expression op expression where op is one of the following: * multiplication / division, result varies if neg integers % modulo div, must not be float + addition subtraction << shift left exp left by right exp bits. Example: byte (1<<2) is 00000100 >> shift left exp right by right exp bits. Example: byte (0xf0>>2) is 00111100 note: pointers may be added or subtracted. See Ritchie for conversion rules. relational operators expression op expression where op is one of the following: (value is int 1 if TRUE) < less than > more than <= less than or equal to >= more than or equal to == equality && logical AND as in A and B are both true || logical OR as in either A or B are true != NOT equal Be cautious of domain problems created with the above. For example, testing a pointer range using if(pointer<limit) when we mean <= and not just <. C and C++ will allow the pointer to move beyond the range of the buffer or array it is indexing and write on variables which just happen to lie nearby causing debugging crosstalk between variables and maybe even functions. Page 6 of 33 Data Objects see procedure & function calls for scope rules declaration: All variables must be declared but declaration does not necessarily reserve storage depending upon scope rules and storage class specification. Mostly definition sets type and declaration creates storage. syntax: <storage class> [modifier ] [type specifier] [variable list] storage class static auto register extern volatile not allocated on the stack, fixed, persistent storage. This is the default for variables declared outside a function whose scope is then also global. allocated on the stack. Does not persist between calls. This is the default for variables declared inside a function including main() try to keep the variable in a CPU register. May not work link to external compiled module, can be return value on stack or static actually information to the compiler that the variable may change between accesses because it can be changed by another process. Ergo, the compiler stores and recovers the variable to register often. No firm definition for exactly when this happens. Register variables do not remain in place between procedures. Notes on storage class and types A storage class list permits the compiler to find a variable so in: MOVE.L Parm1,D1 MOVE.L 4(SP),D1 static storage auto storage the compiler will know the address of Parm1 or even if it should use the first or second instruction. A variable type identifies which routines a compiler should use in: a = b * c; The variables may be short, long, float or double and thus require different multiplication routines and over flow checks. Page 7 of 33 The exact consequence of the volatile storage class varies between compilers and does not protect against access by interrupts. However, declaring a variable to be volatile will prevent some types of optimization and sequence relocation if one also uses a function like barrier( ) which is used to control compiler optimization by re-sequencing operations. type specifiers: primitive type specifiers are: char ASCII, 8 bit (signed, may be used in integer operations). The only type guaranteed to be 8 bits. int integer (32 bit on all 68000 Lattice C, may be 16 on IBM and small processors or even eight bit!) float floating point, (32 bit, signed, +-10E-37 to +-10E38) double floating point, (64 bit, signed, +-10E-307 to +-10E308) Note that integers vary between compilers and machines. For this reason, a file often called compiler.h will typically define things like int8_t, int16_t, uint16_t and int32_t for signed 8 bit, signed 16, unsigned 16 and signed 32 bit respectively (and more types). Use these if you want the code to be portable or just be sure what size the variable might be. In small machines, this level of control is necessary to preserve internal SRAM, NVM and throughput. type modifiers (may be applied to most primitive types) short short integer or just short, 16 bit signed long long integer or just long, 32 bit signed unsigned changes range to 0 to 255 or 65535 or 4294967295 Initialization long count = 0L; sets auto variable to (long) zero int count(0); also sets auto variable to zero using functional notation Note that an uninitialized auto variable may have garbage values unless the compiler or library in use takes care of it. A BSS segment is also unitialized data unless the above is used or the compiler sets it to zero (as is often done if no initializer). examples: int c,i,j,counter,*cn /* pointers must have data types of target */ long c,i,j,counter /* 32 bit integer */ long float c,i,j /* same as double */ unsigned long int c,i,j /* 32 bit unsigned */ Page 8 of 33 Arrays There may be an array of up to X dimensions of any of the types above as in: int MyOneDimArray[30]; // One dimension is also called a vector float MyThreeDimArray[10][11][12]; long MyInitArray[ ] = {1,2,4,5,7}; long MyNullInitArray[2][4] = {0}; // inits all to zero Remember that indices begin from 0 so the elements of the first example are MyOneDimArray[0] .. MyOneDimArray[29]. One can have four dimensional arrays. Microsoft may support up to 32 dimensions. One can also declare an array of structures or pointers. Note that strings are intrinsically a vector of type char and that the string name is actually a pointer to the first array address as is an array of structs. The last char in a string must be \0 (null) meaning a 10 character string needs at least 11 elements. Derived Objects Derived data types may be generated with: typedef [type] [newname]; Examples: typedef char BYTE; typedef unsigned char typedef unsigned char typedef unsigned char /* signed 8 bit quantity */ UBYTE; /* unsigned 8 bit quantity */ BYTEBITS; /* byte value with bit fields */ *STRPTR; /* C string pointer */ struct (ures) An array of dissimilar simple data types or of simple types and other structures or pointers. Example: typedef struct bool_control { CONTROL b_control; /* standard control struct */ UWORD bit_mask; /* mask to find affected bit(s) */ BITSWITCHPTR resource; /* resource containing the bit */ PFI target_method; /* target_method(*this) if target of virtual */ int link_obj; /* link object number if virtual */ } BOOL_CONTROL, *BOOL_CONTROLPTR; Upper case labels are themselves #defined or typedef as for example, UWORD means unsigned word and is replaced with same by the preprocessor. PFI is interesting being a pointer to Page 9 of 33 function returning integer. In ANSI C, functions could be defined as part of the structure more directly. typedef int (*PFI) (); The struct itself is typed so that one could have: BOOL_CONTROL gain_control; which would allocate memory for a Boolean control structure called gain_control so that one could write: gain_control.target_method(gain_control); However, if the pointer were defined as: BOOL_CONTROLPTR p_gain_control; then the call would be: p_gain_control->target_method(gain_control); The characters -> indicate dereference or that the name is a pointer. In C and C++, one tells the compiler that the name is a pointer if it is not otherwise defined by the language (as in the case of string names). However, the IDE may supply -> if you type ‘.’ If in fact the name is a pointer to a structure. Other examples of declarations: static struct IntuiMessage *msg,*GetMsg(); struct NewWindow MyWindow; Union struct (ures) An unusual structure still used in embedded software having small memory is the union. Operating systems may use it as part of IPC1 but it’s not recommended in general since saving memory is not typically a goal. An example is shown below. 1 Inter Process Communications Page 10 of 33 typedef struct exec_message { //THERE ARE ONLY FOUR FIELDS HERE exec_PID_t from, // field 1 exec_msg_t type // field 2: needed to describe contents of the next fields union { CMD_t command; ERROR_CODE error; exec_info_t info_msg; } msg_info; // field 3 //! data field is zero unless 'type' above is data_msg. union { uint16_t uint16_data; uint16_t *uint16_ptr; int16_t int16_data; int16_t *int16_ptr; float float_data; char *char_ptr; } msg_data; // field 4 } EXEC_MSG, *EXEC_MSG_PTR; The above union actually has four fields: from type msg_info msg_data The latter two may be of various types and sizes depending on the type of message. This is a named union but there are also anonymous unions where the union is not itself identified (e.g. msg_info). One sets a union element as in: EXEC_MSG cmd_msg; cmd_msg.from = PROCESS1; // non-union elements in the usual way cmd_msg.type = COMMAND; cmd_msg.msg_info.command = TURN_OFF; // setting a union member The named union is handled as if it is a substructure with the particular type being a sub-subelement. One can initialize a union thus: Page 11 of 33 EXEC_MSG cmd_msg = { PROCESS1, // from us COMMAND, // it's a command type {TURN_ON}, // handle unions like sub-structs... {0} // no numerical data }; // cmd_msg Enumerated A variable whose permissible values are constrained by it’s type definition. The keyword typedef is not required. typedef enum { FLOAT_NO_LABEL=0x00, /* floating point but no label */ FLOAT_PERCENT=0x10, /* a floating point percentage */ FLOAT_HZ=0x20, /* a floating point frequency in Hz */ FLOAT_LINES=0x30, /* floating point number of lines in format */ INTEGER_NO_LABEL=0x40, /* integer type but no label */ BOOL_LABEL=0x50, /* ON or OFF */ STRING_MSG=0x60, /* the message is a string */ STRING_ARRAY_MSG=0x70 /* the message is an array of ptrs to strings like argv */ } DATA_LABEL,*DATA_LABELPTR; If the ‘=’ is not supplied, then constants of DATA_LABEL type are assigned in sequence the values 0,1,2,3… For any name, you can assign a value and the subsequent values will be one larger. The above example is assigning the meanings of a bit mask in an unusual way. Example 2: typedef enum { CONTRAST=1, BRIGHTNESS, RED_DRIVE, GREEN_DRIVE, BLUE_DRIVE, RF_FREQUENCY, RF_BANDSWITCH, COLOR, HUE_DECODER, H_PHASE, /* begin prop_control objects */ /* note that cont,brite,drives & bkgnds */ /* are all virtual controls */ /* tuning */ /* DAC that controls the bandswitch */ /* controls the decoder */ /* controls position on pulse bd */ In the above example, the first value = 1 instead of 0 with the rest incrementing 1. This is convenient for case statements which do not handle value 0. Page 12 of 33 Procedure and Function Calls Calling Method: There is no difference between procedures and functions in C. Arguments are passed by value (copied and pushed on the stack). An argument may be passed by reference by passing a pointer to the argument. However, arrays, structs and strings are always passed by reference2. See section on pointers. Examples: ret = [procedure_name](arg1, arg2, ..., argn); where: ret and [procedure_name] are the same type or ret = (ret_type *)[procedure_name](...) is cast same. ret is optional. In this case, make function VOID. argx is optional. Use (), empty parenthesis if none. Functions have a declared type. Examples: int factorial( ); float determinant( ); VOID closewindow( ); //type VOID returns no value Function Prototypes It’s possible to define a function multiple times as long as all of them are the same. Only one may contain the code. The rest are function prototypes, typically in a header file, used to eliminate a common C language coding error…that the arguments in the call do not match the arguments in function code. The result is often an application crash. Example: int prop_save(PROP_CONTROLPTR prop_this, OBJECT_DATAPTR data); Types in capital were previously #define ed as structure pointers. 2 Although some compilers may require that you specifically include the ‘&’ to indicate a pointer or otherwise declare it Page 13 of 33 Branching and Conditional Remember that (Boolean) TRUE=1 and FALSE=0. Expressions are actually compared to FALSE (=0) and if not, then assumed TRUE. If expression is variable = 0, then FALSE condition will be satisfied. if/then/else if (expression) statement else statement A common error not caught by the compiler is to do this: If ( sum = 10) counter++; Actually, the expression will always be true and counter will always increment. That’s because one intended to write: If ( sum == 10) counter++; One intended to write, “If sum is equal to 10, then increment counter” but that is not what the first version says. It says to assign 10 to sum and then test if sum is equal to zero (of course it’s not!) so then it’s TRUE and counter is incremented. The operators ‘=’ and ‘==’ are not the same. while --- do while while (expression) statement /* expression tested before executing statement */ do --- do until do statement while (expression); /* expression tested after executing statement */ for loop for (<expression1>;<expression2>;<expression3>) statement Expression1 is executed before the loop begins, expression2 is tested before statement is executed and expression3 is executed after statement. If expression2 missing, then it is equivalent to having while(1) below. Page 14 of 33 Example: for (n=0,n<20,n++) sum = sum + n; equivalent to: n=0; while (n<20) { sum=sum+n; n++; } case statement switch (expression) statement expression must be type int. statement is typically compound with labels of the form: { case constant-expression1: case const2: statement1;break; case constant-expression3: statement2;break; default: default_statement;break; } The case expressions must be of type integer and no two the same value. The break statement is required or the next statement will be executed (crude, isn’t it?). statementx may be compound itself. Multiple labels are permitted. break; causes termination of the smallest enclosing branch statement. continue; causes control to pass to the loop-continuation portion of the smallest enclosing branch statement. Remember that TRUE=1 and FALSE=0. Expressions are compared to FALSE (=0) and if not, then assumed TRUE. If expression is variable = 0, then FALSE condition will be satisfied. Exit from function (before end of possibly compound statement) or exit from and return value of expression. goto statement goto identifier; See! This ain’t PASCAL. Page 15 of 33 Pointers C has direct control of some machine operations to include pointers or indirection. A typical machine operation would be: MOVE.(A2),D1 MOVE.(A3)+,D2 move indirect address reg 2 to data register 1 move indirect address reg 2 post increment to data register 2 In C code for this machine, one could write: result = *data1 + data2; // data pointed to by data1 add to data2 and perhaps add data1++; // increment the pointer address by the size of its type The first C code line above might result in just three lines of assembler whilst the second would only change one of the assembler lines to include address register post increment. This is rather tight correspondence between C and machine code but at least it would be machine portable. Also, one could set pointer variable to a machine address as in “data1 = 0xfffc1234;” (more likely a #define hardware register name) and thereby be able to use C code to directly control machine hardware. C and C++ can be used to write an operating system through the use of pointers. It is possible to use double indirection or have an array of pointers to other pointers. The classic example is the command line argument list, argv from “main(argc,argv)”: char **argv; // argv is an array of pointers to character (strings) Page 16 of 33 Examples: int *p_timer; // p_timer is a pointer to an integer data type int* p_timer; // this is the same thing as above in alternate form p_timer = &timer_two; // &timer_two means ‘get the address of timer_two’ *p_timer = *p_timer + increment; /* add increment to data pointed to by p_timer and put that back in the address pointed to by p_timer */ p_timer += increment; /* increment the pointer by “increment X sizeof(*p_timer)” */ sum += *p_array_data++; /* sum the contents of what p_array_data is pointing at and then increment the data by one */ sum += *(p_array_data++); /* sum the contents of what p_array_data is pointing at and then increment the pointer by one */ Note that the indirection operator ‘*’ has a higher priority than arithmetic operators which causes the above idiosyncrasy. The dot operator is higher priority than * dereference. One can use -> to be clear. See the C++ section Simple Parent Class. Note that an array, structure or string name is automagically a pointer. In Line Assembler Most C compilers support in line assembly programs or the writing of assembler code directly into a C program. This is useful in the case of an often repeated routine that must operate very efficiently such as digital signaling operations. Alternately, it may be used for critical timing and sequencing in the use of certain hardware. A compiler will probably optimize out any busy wait looping instructions that might be used to make hardware perform correctly but you can be sure of the timing of assembler programs. In line assembler has easy access to static C routine variables in domain. In line assembler is compiler specific and obviously processor specific. Consult the manual. This is just one example for an Atmel AVR processor and the Atmel adapted gcc compiler. The general format in this specific case is: asm(code : output operand list : input operand list [: clobber list]); In the code section, operands are referenced by a percent sign followed by a single digit. 0 refers to the first 1 to the second operand and so forth. For example: asm("in %0, %1" : "=r" (value) : "I" (_SFR_IO_ADDR(PORTD)) ); Page 17 of 33 From the above example: 0 refers to "=r" (value) and 1 refers to "I" (_SFR_IO_ADDR(PORTD)). The last part of the asm instruction, the clobber list, is mainly used to tell the compiler about modifications done by the assembler code. See the compiler manual as this is all highly compiler and machine specific. Input/Output in Standard Library printf/fprintf/sprintf --- formatted output #include “stdio.h” printf(control_string, arg1, arg2, ...); /* stdout, see below for control_string */ FILE *fopen( //should be fp = fopen( .. ). Return NULL is error const char *filename, // filename is a string or include explicitly const char *mode // mode should be w .. writing to use fprintf( ) ); fprintf(fp, control_string, arg1, arg2, ...); /* file, open first */ n=sprintf(ds, control_string, arg1, arg2, ...); /* string */ int n; /* number of chars transferred */ FILE *fp; /* file pointer. args are variables to print */ char *control_string, *ds; /* ds is destination string */ example: printf(“pi= %d”,pi); The control string may be included explicitly. It may also include conversion specifications the syntax of which is: %<-><m><.><n><l>[convert] where: left justify (else right justify) m minimum field width . separator required to include numerical precision n precision, max char from string or digits to right of float l the data item is long and not type int convert (character) is: d use decimal in output o use unsigned octal Page 18 of 33 x use unsigned hex u use unsigned decimal c argument is single character s argument is string e use exponential <->m.nnnnnnE<+->xx (default n 6) f use fixed point <->mmm.nnnnn (default n 6) g use e or f whichever is shorter Page 19 of 33 scanf/fscanf/sscanf --- formatted input #include “stdio.h” n=scanf(control_string, pointer1,pointer2,...); /* stdin */ n=fscanf(fp,control_string,pointer1,pointer2,...);/* file, open 1st */ n=sscanf(ss,control_string,pointer1,pointer2,...); /* string */ int n; FILE *fp; /* file pointer. pointers are to input variables */ char *ss,*control_string; example: n = scanf(“%2d %f %*d %2s”, &i, &x, name); where %.. input conversion is as in printf, and n is no. items matched or EOF and * means skip so that with input: 56789 0123 45a72 is read as: 56->i, 789.0->x, skip 0123,45->name note: a72 is left in input channel for next scanf()! name is already type pointer. control_string may be included explicitly. fflush(fp); FILE *fp; /* fp file pointer, forced output buffer clear */ setnbf(fp); FILE *fp; /* deletes I/O buffer for immediate I/O, e.g. console */ Page 20 of 33 gets/fgets --- get a string p=get(s); / * stdin */ p=fgets(s,n,fp); /* file */ char *p, *s; /* p returned string pointer, s buffer for input str */ int n; /* n number of bytes in buffer */ FILE *fp; /* fp file pointer */ Get an input string from a file, stdin for gets. File is read until newline char or n-l char has been read (fgets only). gets replaces newline char with NULL byte and fgets appends NULL byte. Useful for free form input. Caution: gets can crash the system if *p too small. fopen/freopen --- open a buffered file fp = fopen(name, mode); fp = freopen(name, mode, fpx); FILE *fp,*fpx; /* fp file pointer */ /* note: Ritchie recommends FILE *fopen(),*fp,*fpx; */ char *name,*mode; /* name is file name, mode access mode */ where: name includes the path mode is one of the following: r reading, w writing, a appending note: fp==0 if error. fclose --- close a buffered file ret=fclose(fp); int ret; /* return code */ FILE *fp; /* pointer to file to be closed */ where: ret==-1 if error and 0 successful clrerr/clearerr --- clear error flag for file clrerr(fp);clearerr(fp); FILE *fp; /* file pointer */ note: Once error flag is set, it will remain set untill cleared. Page 21 of 33 Scope of Variables Example: /* first file to be compiled */ #include <stdio.h> /* preprocessor directives */ struct Window *MyWindow; /* global or external variable */ struct NewWindow *MyNewWindow; /* because outside of any procedure*/ main(argc,argv) { int counter,limit,*parameter; /* local to main because inside of main() statement */ extern float pie; /* global declaration (not definition) of float variable pie with key word extern from this point on */ ...; } #include <mydirectory/next_procedure> /* procedure next_procedure will have globals *MyWindow and *MyNewWindow because compiled at same time */ /* end of the first file */ -----------------------------------------------------/* second file to be compiled */ #include <stdio.h> /* preprocessor directives */ extern struct Window *MyWindow; /* must be extern or duplicate definition */ int foo(arg1) /* scope of arg1 is function foo() */ { int counter; /* local variable. This is not the same variable as in file 1 */ int sum = 0; /* initialized local variable */ for (counter=0;counter<20;counter++) { float sum = 0; /* very local variable, good for length of block (compund statement). Not the same as int sum in rest of procedure */ ...; } /* end of block and float sum. Now int sum is effective */ ... } /* end of file two */ Page 22 of 33 Subtle Errors Enabled by C Language C Language was developed as a higher level language (actually medium level) to be used in writing OS, Operating Systems. As such, it is able to read and write directly to hardware via it’s ability to create any microprocessor instruction whatever. Buffer Over-run Circular buffers are common each of which needs two indices, input and output. Perhaps the buffer is BUF_LEN long so then one might use array indices as in my_buffer[out_index] and always increment the indices up. Then, to check if we reached the end of the buffer: char my_buffer[BUF_LEN]; // array of characters used as a buffer int in_index = 0; // indices into the character array int out_index = 0; … // operations on buffer in and output here If (out_index++ < BUF_LEN) out_index = 0; The above code has a sequence error in that the index is post incremented after the compare rather than before (++out_index) which causes the index to move past the end of the character array and out of the buffer depositing a character on whatever variable happened to be there. Most of the embedded compilers will let this happen. Consider that BUF_LEN is 10 in which case the last character is my_buffer[9] (the index runs 0..9 for 10 elements). When out_index is 9, we compare to BUF_LEN which is 10 and find it less so we post increment to out_index = 10 rather than resetting to the beginning. Then the buffer operations will use my_buffer[10] which is probably another variable in the memory causing a mysterious failure perhaps in some completely unrelated routine! There are even more ways to make this happen. Pointer Addressing The above example uses array indexing but often we do: char* my_pointer; // now a pointer to a character or byte char my_buffer[BUF_LEN]; // an array of characters my_pointer = &my_buffer; // pointing to the first character of the array/buffer Page 23 of 33 Then one can use operations like: my_pointer++; *my_pointer = ‘A’; However, it is easily possible in the same way as before to increment right off the end of an array…even easier. Harvard Architecture Problems ANSI C implicitly assumes that the target machine has one linear address space and that pointers can access it all. This might not be true. One might declare a pointer to a function thus: typedef (*PFV) (void); // PFV is intended to mean pointer to void function So that we can define a structure: typedef exec_task { PFV cold_constructor; PFV warm_constructor; PFV object_run; } EXEC_TASK; // initialize the object // reinitialize the object // do work And then some data: int speed = 55; int* data_pointer[10]; data_pointer[3] = &speed; Then you can define an instance of the function structures as an array of them: EXEC_TASK object_table[ ] = { {constructor1, warm_constructor1, obj_run1}, {constructor2, warm_constructor2, obj_run2} }; Page 24 of 33 The above is an array of pointers to (void) functions and above that we have data pointers, e.g. data_pointer[3]. BUT…there are at least two kinds of address space in a Harvard Architecture CPU, code space and data space. Is the compiler smart enough to keep these two pointer domains separate? You may look up pointers in a small Harvard computer and find that they are always 16 bits but then the code address space could be something up to 500k+ bytes. 16 bits cannot point to all of 500k or even 128k of address space. It can only point to addresses 0..65535 or 64k. So must the PFVs be in the lower 64k and if so, how do you know they are in that address range? C and C++ Compiler designers for small Harvard Architecture Computers3 must pay particular attention to address space and often supply, transparently4, a mechanism called trampolines so that pointers to functions, an intrinsic part of C++, will work. Indeed, if the data space becomes too large, they must also supply extended addressing for data as well. Trampolines are jmp or jump instruction tables at a known part of the FLASH code space, often somewhere near the beginning. If the program counter cannot address all of the code space, the jump table may occupy extra space to set address bits. When the compiler implements a pointer to a function, it loads an index register with the address of this table and then jumps to the instruction at the register + (integer) pointer value (or 2X or 3X pointer precomputed) which is a jump or jsr (jump subroutine) instruction to the real function address. The jump table is constructed so that we also return to the address after the call when a rtn (return) instruction is executed. You don’t need to know about this action for it to work. In contrast, you must take deliberate action to save your perhaps limited (S)RAM from exploitation by constants. What would constants be doing in SRAM? Of course, we frequently use pointers to constants. In fact, every reference to a string is a pointer to the first character of a character array5. If we have the situation with 16 bit pointers and the FLASH is say 256k, then how does the following work? printf(“Hello World!\n”); 3 These are often RISC type as well Maybe not to you since if you knew about the 16 bit function pointers, you would be confused 5 Terminated by a NULL, zero 4 Page 25 of 33 Hello World is a string that must certainly be stored in (256k) FLASH when the program begins. How does a 16 bit pointer access it in FLASH? It doesn’t. The compiler also makes a second location for the string in (S)RAM and just before main( ) executes, it runs its own initialization program which copies ALL the strings into RAM. If you have a lot of strings…the RAM is gone before you do any work! A quick peek at memory space just before main( ) starts in debug mode should convince you of how this works6. Here is an example from an XMega. The regular library printf function takes pointers to strings and pointers ONLY work in RAM so the character strings are all copied to RAM for you. BUT that means there must be a way to get constant data from the FLASH. Atmel supplies a second printf function, printf_P( ) meaning constant strings print from program memory. printf_P(PROGMEM_STRING("Change freq = %ld\n"),hz); You need also to declare the constant string in FLASH as well. There are other functions and requirements for using data in FLASH on the Atmel AVR Harvard RISC machines and their gcc compiler. See their manuals7. Other Harvard machines have similar problems and fixes when using C and C++. 6 7 E.g. on Atmel AVRs like Mega and XMega Which are hidden in the program directory on a PC and not in user data space Page 26 of 33 C++ See above for ANSI C. Below find only the additional C++ features which include OOPS (classes and related), operator overloading and polymorphism. This is just simple C++ and not any of the tricks or tool kits like MSoft MFC. Small processors usually do not have expansive libraries with perhaps the exception of Windows and OS7 mobile versions. Those typically require a processor having Von Neumann, linear address space such as in the quad ARM series of processors used by the Windows Surface2 series8. Both OSes have free IDE development system downloads for mobile applications with support for mobile being part of the professional development system. See also the WROX C++ tutorial on line. My opinion is that the major C++ improvement over C Language is OOPS9 support with assured initialization especially in embedded software where the OS may be too simple to provide that service. Disadvantages include slower execution10, sometimes obscure constructor operation11 and difficulty in controlling target memory selection so that sometimes code ends up assigned to RAM in a mobile system…not possible. This last problem has forced me in times past to use C code and implement the OOPS paradigm manually. The same and more happened to Microsoft with one of their earlier OS versions which partially explains the mix of methods and data types in their API. Class (Structures) A class is a named structure which explicitly permits inclusion of procedures (functions) without C language pointer patching12. The intent is to create a software object having all necessary data and methods (functions) to operate on the included data. Classes may be instantiated at compile time as in C (early binding) or created on the fly using the new operator13 (late binding). Classes may exhibit inheritance. That is, they main contain or inherit other classes even if using late binding. It may be intended that methods be replaced at run time (place keeper or virtual functions/methods)14. 8 Harvard Architecture exists at the cache memory level where the processors sort data and code from the common memory space into separate cache segments. However, this method is C language compatible. 9 Object Oriented Programming System which is a paradigm and not a language. It is possible to use the OOPS method in C and even assembly, sometimes with better success than using C++ 10 Offset by very high clock speeds 11 This may be most apparent using late binding or the new operator to create new instances of objects/classes 12 This example back in the C Language section 13 This is a significant problem for embedded processors having small RAM. It is not necessary to cast newly allocated memory into a struct as in C. 14 Another problem for embedded processors Page 27 of 33 Simple Parent Class A named structure type with at least one of public or private and restricted data. Example: class dog { private: dog* body; char* name; dog *tail; public: dog(char* dog_name); char* dog_name( ); void create_tail(char* name_of_tail); char* get_name_of_tail( ); }; // note semicolon The above could contain a section called protected: Note that a class can have itself as a member as for example to implement a linked list. A class as a member is actually a pointer to the class. Functions may be implemented in the declaration as in: char* dog_name( ) {return name}; The above code will be implemented in-line if it’s shown in the declaration which is usually in an *.h file even without an in-line declaration. If the function is implemented separately, it is a called procedure or subroutine as in: char* dog::dog_name( ) { return name; } // no semicolon ‘::’ is the scope operator meaning that dog_name is in the scope of the class dog. Something like ‘::dog_name’ (without dog) is ‘world scope’ or global and is perfectly lega. If we have at compile time or early binding object: dog silky_terrier(“Lacy”); Page 28 of 33 then use: char* name_string; name_string = silky_terrier.dog_name( ); If there is a run time created or late binding object: dog* silky_terrier = new dog(“Lacy”); then use: char* name_string; name_string = silky_terrier->dog_name( ); As explained in the sections above on structs and pointers, the arrow -> dereferences the pointer silky_terrier. Interestingly, this also works but not used very much: name_string = (*silky_terrier).dog_name( ); It’s clumsy because the dot operator would have preference over * dereference so the parenthesis is required. Derived Class A derived class inherits its own copy of the parent class data and methods but can only access those in the public section without re-declaring them or including the public keyword in the definition meaning everything in the parent is public to the derived class and thus accessible without redefining. It is very common to see the public keyword in the definition. Page 29 of 33 Example: class parent_class { private: int private1, private2; public: parent_class (int p1, int p2) { //constructor private1 = p1; private2 = p2; } void assign(int p1, int p2) { private1 = p1; private2 = p2; } int inc1( ) {return ++private1;} … }; Page 30 of 33 class derived_class1 : parent_class { private: int private3; parent_class private4; // it has included one of the parent class public: derived_class1(int p1,int p2,int p3,int p4,int p5) : (p1, p2), //constructor private4(p3,p4) // inits also internal parent class { private3 = p5; } void assign(int p1, int p2) { parent_class::assign(p1, p2); // :: is the scope operator { int inc1( ) { return parent_class::inc1( );} … }; In the above derived class constructor, after the colon, arguments p1 and p2 go to initialize (construct) the intrinsically included parent class object, p3 and p4 go to initialize the explicitly included parent object private4 and the remaining parameter p5 goes to the derived class constructor as shown in the statement “private3 = p5;” . Intrinsically included parent class constructors are always called first. A program using the above might be: main( ) { derived_class1 d1(17,18,1,2,-5); d1.inc1( ); … } Page 31 of 33 Constructors & Destructors See also section Simple Parent Class for new and delete. A member function (method) of a class by the same name is a constructor. See comments in examples of sections Simple Parent Class and Derived Class. There may be multiple constructors distinguished automatically by the number or type of arguments (DANGER! This is not true if object creation is by a pre-compiled and unlinked library like DLLs or the OS. Typically, the first parameter then describes which constructor or number of arguments are used). The intrinsically included parent class object is initialized first (if any constructor, that is called) and then the derived class constructor. This can all be tiered further. Scope (Public/Restricted/Private) See also section Scope of Variables in the section on ANSI C above. See also the scope operator :: in sections Simple Parent Class and Derived Class above. Embedded SW Problems Part of the code for initializing a class and especially a contained parent in a derived class is in the (invisible) compiler/linker libraries. If the development suite was not intended for firmware or they forgot to set the constructor (or other necessary) code segment as being in ROM (as in segment BCC), then it could land in RAM. This might not be apparent during development as the RAM is powered up continuously and only appear once the application or other code is flashed. Operator Overloading A method in a class which is invoked by one of the standard C/C++ operators such as +, -, *, =,++ or even ( ). More operators can be overloaded. Consider a class of integer counter register which runs the range of 0 to 2^16 – 1 without rolling over. Then it would be convenient to use ‘++’ for increment instead of calling an increment method as in ‘c1.increment( );’ where c1 was of the class ‘counter.’ void counter::operator ++ ( ) { if(value << 65535) value++; } Polymorphism This is a method marked by the keyword virtual. TBD Page 32 of 33 Subtle Errors Enabled by C++ Constructor Chaos Virtual Functions in ROM(!) Page 33 of 33
© Copyright 2024