Ce diaporama a bien été signalé.
Le téléchargement de votre SlideShare est en cours. ×

Embedded C - Lecture 2

Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Prochain SlideShare
Embedded C - Lecture 3
Embedded C - Lecture 3
Chargement dans…3
×

Consultez-les par la suite

1 sur 95 Publicité

Embedded C - Lecture 2

Télécharger pour lire hors ligne

* Preprocessor directives (continue).

* Data types (Primitive, derived, user defined).

* Qualifiers (Size, Sign and volatile modifiers and Storage classes).

* Scope and lifetime.

* Makefile introduction.

* Preprocessor directives (continue).

* Data types (Primitive, derived, user defined).

* Qualifiers (Size, Sign and volatile modifiers and Storage classes).

* Scope and lifetime.

* Makefile introduction.

Publicité
Publicité

Plus De Contenu Connexe

Diaporamas pour vous (20)

Les utilisateurs ont également aimé (20)

Publicité

Plus récents (20)

Publicité

Embedded C - Lecture 2

  1. 1. Prepared by: Mohamed AbdAllah Embedded C-Programming Lecture 2 1
  2. 2. Agenda  Preprocessor directives (continue).  Data types (Primitive, derived, user defined).  Qualifiers (Size, Sign and volatile modifiers and Storage classes).  Scope and lifetime.  Makefile introduction.  Task 2. 2
  3. 3. Agenda 3
  4. 4. Preprocessor directives (continue) 4
  5. 5.  Examples on preprocessor directives (continue): • Function-like Macro: #define MAX(a,b) ((a)>(b)) ? (a):(b)) Define macro to be used like a function call, so typing: int maximum_num = MAX(2,7); will be replaced with: int maximum_num = ((2)>(7)) ? (2):(7)); Which will make maximum_num value be 7 5 Preprocessor directives (continue)
  6. 6.  Examples on preprocessor directives (continue): #define OUTPUT 1 #define LOGIC_HIGH 1 #define LED_INIT(PORT,PIN) portInit(PORT,PIN,OUTPUT); portState(PORT,PIN,LOGIC_HIGH) Define macro to be used like a function call, so typing: LED_INIT(‘A’,3); will be the same as typing: portInit(‘A’,3,OUTPUT); portState(‘A’,3,LOGIC_HIGH); Note: Function-like Macro is like normal macro, it is just text replacement. 6 Preprocessor directives (continue)
  7. 7.  Pros and Cons of using Function-like Macro • Function-like macro usage is just copy and paste, so no function call actually occurs which makes it faster than function call. • It is more difficult to debug in function-like macro code than normal function code. • Macros are error-prone because they rely on textual substitution: #define square(a) a*a /* works fine when used with an integer */ square(5); /* --> 5*5 --> 25 */ /* but does very strange things when used with expressions */ square(1+2); /* --> 1+2*1+2 --> 1+2+2 --> 5 */ square(x++); /* --> x++*x++ --> increments x twice */ 7 Preprocessor directives (continue)
  8. 8.  Pros and Cons of using Function-like Macro Using parentheses around all your variables inside macro will help in first problem but not the second: #define square(a) ((a)*(a)) square(1+2); /* --> ((1+2)*(1+2)) --> 3*3 --> 9 */ square(x++);/*((x++)*(x++))-->still increments x twice*/ • Macros do not perform type-checking: #define MAX(A,B) (((A)>(B)) ? (A) : (B)) Then doing the following will not produce any error or warning: printf("%dn", MAX("abc","def")); But if the same was done with usal functions then the compiler will generate warning during compilation. 8 Preprocessor directives (continue)
  9. 9.  Examples on preprocessor directives (continue): • Stringification: #define LED_INIT(PORT,PIN) printf("Initializing PORT: "#PORT" n"); portInit(PORT,PIN,OUTPUT); portState(PORT,PIN,LOGIC_HIGH) When we want to convert a macro argument into a string constant. So typing: LED_INIT(‘A’,3); will be the same as typing: printf("Initializing PORT: A n"); portInit(‘A’,3,OUTPUT); portState(‘A’,3,LOGIC_HIGH); 9 Preprocessor directives (continue)
  10. 10.  Examples on preprocessor directives (continue): • Concatenation: It is used to merge two tokens into one while expanding macros. This is called token pasting or token concatenation. Suppose you have a function with 3 different implementations: portInit_implementation1(void); portInit_implementation2(void); portInit_implementation3(void); Then you can define: #define PORT_INIT(NUM) portInit_implementation ## NUM () Then to choose which function of the 3 to call using only PORT_INIT: PORT_INIT(2); it is the same as typing: portInit_implementation2(); 10 Preprocessor directives (continue)
  11. 11. Data types 11
  12. 12. 12 Data types
  13. 13.  Primitive Data Types • The primitive data types in c language are the inbuilt data types provided by the c language itself. Thus, all c compilers provide support for these data types. • The following primitive data types in c are available:  Integer Data Type, int: Integer data type is used to declare a variable that can store numbers without a decimal. The keyword used to declare a variable of integer type is “int”. Thus, to declare integer data type following syntax should be followed: int variable_name;  Float data Type, float: Float data type declares a variable that can store numbers containing a decimal number: float variable_name; 13 Data types
  14. 14.  Primitive Data Types  Double Data Type, double: Double data type also declares variable that can store floating point numbers but gives precision double than that provided by float data type. Thus, double data type are also referred to as double precision data type: double variable_name;  Character Data Type, char: Character data type declares a variable that can store a character constant. Thus, the variables declared as char data type can only store one single character: char variable_name;  Void Data Type, void: Unlike other primitive data types in c, void data type does not create any variable but returns an empty set of values. Thus, we can say that it stores null: void myFunc(); /* Function returns void data type */ 14 Data types
  15. 15.  Derived Data Types • A derived data type is a complex classification of a new data type that is made up of simpler primitive data type. Derived data types have advanced properties and uses far beyond those of the basic primitive data types that operate as their essential building blocks. • The following are examples for derived data types:  Array: a kind of data structure that can store a fixed-size sequential collection of elements of the same type. So instead of typing: int var1, var2, var3, var4; We can simply make them in array of 4 integers: int Arr[4]; /* Uninitialized */ OR int Arr[4] = {3,2,4,6}; /* Initialized */ Array consists of contiguous memory locations. The lowest index Arr[0] corresponds to the first element and the highest index Arr[3] to the last element. 15 Data types
  16. 16.  Derived Data Types (Array continue) Array can be given value at declaration (Initialized array) or can have no initial values at declaration. Array should be fixed size, and size should equal number of initialization elements: int Arr[4]; /* Valid*/ int Arr[] = {3,2,4,6}; /* Valid, compiler will put size = 4 as it is initialized */ int Arr[]; /* Invalid, compiler error */ int Arr[2] = {5,6,7,8}; /* Invalid, compiler error */ Accessing array is through array name and element index: Arr[2] = 10; /* Change third element */ Exceeding array limits: int Arr[10]; /* Declare array of 10 integers*/ Arr[15] = 10; /* Will not produce compilation error, but at runtime it may cause a lot of problems (Data corruption, program termination, …) */ 16 Data types
  17. 17.  Derived Data Types (Array continue) String doesn’t exist as a primitive data type in C language, but arrays can be used to represent string in an array of characters: char Arr[6] = ‚Hello‛; Note that array size is number of string characters + 1, this extra character is used to store the NULL ‘0’ terminator which marks the end of string, so the following will lead to same result: char Arr[6] = {‘H’,’e’,’l’,’l’,’o’,’0’}; This NULL terminator is used to know where string ends, for example when required to print the string: printf(‚%s‛, Arr) /* Will print Hello*/ Or by doing: int i = 0; while(Arr[i] != ‘0’) printf(‚%c‛, Arr[i++]); /* Will print Hello*/ 17 Data types
  18. 18.  Derived Data Types (Array continue) Till now this is called 1-D (1 dimensional) array. In memory it looks like that: 18 Data types Arr[0] Arr[1] Arr[2] Arr[3]
  19. 19.  Derived Data Types (Array continue) C language also provides a multi-dimensional array, for example we can create a 2-D array: int Arr[3][4]; Which can be considered as a table with 3 rows and 4 columns: 19 Data types column 0 column 1 column 2 column 3 row 0 Arr[0][0] Arr[0][1] Arr[0][2] Arr[0][3] row 1 Arr[1][0] Arr[1][1] Arr[1][2] Arr[1][3] row 2 Arr[2][0] Arr[2][1] Arr[2][2] Arr[2][3] Generally we can any number of dimensions: int Arr[size1][size2]………..[sizen];
  20. 20.  Derived Data Types (Array continue) 2-D array initialization: int Arr[3][4] = { {1,22,13,40}, /* row 0 */ {7,4,45,7}, /* row 1 */ {23,34,83,47} /* row 2 */ }; It can be also initialized like that, but this is not recommended for readability: int Arr[3][4] = {1,22,13,40,7,4,45,7,23,34,83,47}; 20 Data types
  21. 21.  Derived Data Types  Function: A function type describes a function with specified return type. A function type is characterized by its return type and the number and types of its parameters. A function type is said to be derived from its return type, and if its return type is int , the function type is sometimes called function returning int. /* Function that returns integer, and takes 2 parameters first is integer and second is character*/ int myFunc(int x, char y); This is called function declaration, or function signature. 21 Data types
  22. 22.  Derived Data Types  Pointers: A pointer is a variable whose value is the address of another variable, i.e., direct address of the memory location: int Var1 = 10; /* Var1 is integer contains value 10 */ int *ptr = &Var1; /* ptr is pointer to integer, its value is the address of Var1 in memory*/ 22 Data types Memory Var1 = 10 0xFE00 Ptr = 0xFE00 0xFF04
  23. 23.  Derived Data Types (Pointers continue) We can make the pointer points to another variable during runtime: int Var1 = 10, Var2 = 30; int *ptr = NULL; /* NULL Pointer points to nothing */ ptr = &Var1; /* Pointer now holds Var1 address */ *ptr = 20; /* Change Var1 value through pointer */ ptr = &Var2; /* Pointer now holds Var2 address */ *ptr = 50; /* Var2 will change to be 50 */ 23 Data types
  24. 24.  Derived Data Types (Pointers continue) Pointer size: Pointer size depends on a lot of factors (hardware, operating system, compiler, etc.), and not all pointer types on the same platform may have the same size. For example, there are embedded processors that use a Harvard architecture, where code and data are in separate memory areas, and each may have a different address bus size (e.g., 8 bits for data, 16 bits for code). This means that object pointers (int *, char *, double *) may be 8 bits wide, but function pointers (int (*)()) may be 16 bits wide. So on 32-bits address bus controller: char *ptr; printf(‚%d‛, sizeof(ptr)); /* Print 4 bytes */ 24 Data types
  25. 25.  Derived Data Types (Pointers continue) Pointer arithmetic: A pointer in c is an address, which is a numeric value. Therefore, you can perform arithmetic operations on a pointer just as you can on a numeric value but the result is different than normal variables. There are four arithmetic operators that can be used on pointers: ++, --, +, and - . When we type: long int Var1 = 10; long int *ptr = &Var1; ptr ++; If Var1 address is for example 1000, then pointer value at the start is the same as Var1 address which is 1000. After ptr++ the value of ptr will become 1004, which means it is incremented by 4 not just 1. Pointer increment by 1 will increment pointer value by the size of the variable it points at which is 4 bytes in our case. 25 Data types
  26. 26.  Derived Data Types (Pointers continue) Another example: long int Var1 = 10; long int *ptr = &Var1; ptr += 3; If Var1 address is for example 1000, then pointer value at the start is the same as Var1 address which is 1000. Adding 3 to ptr will make ptr value becomes 1012, which means it is incremented by 12 (3 * 4 bytes = 12). The same for other increment and decrement operations. 26 Data types
  27. 27.  Derived Data Types (Pointers continue) Pointer can point to any data type, for example pointer to array: long int Arr[5] = {10,20,30,40,50}; long int *ptr = &Arr[0]; This will make ptr points at the first element in the array so: printf(‚%d‛, *ptr); /* Will print 10 */ printf(‚%d‛, *(ptr+1)); /* Will print 20*/ printf(‚%d‛, *(ptr+2)); /* Will print 30*/ *(ptr+4) = 0; /*Will change last element in Arr to 0*/ Pointer can also be used like array with index: printf(‚%d‛, ptr[2]); /* Will print 30 */ ptr[4] = 3; /*Will change last element in Arr to 3*/ 27 Data types
  28. 28.  Derived Data Types (Pointers continue) Pointer can be used to represent string like arrays used: char *ptr = ‚Hello‛; printf(‚%s‛, ptr); /* Will print Hello */ This is the same as typing: char ptr[] = ‚Hello‛; Note: Array name is itself a pointer to the first element in the array, but it is a constant pointer so Arr value can’t be changed by any means: char Arr[6] = ‚Hello‛; Arr++; /* Compilation error */ 28 Data types
  29. 29.  Derived Data Types (Pointers continue) We can create an array of pointers: long Var1 = 1, Var2 = 2, Var3 = 3; long int *ptr[3] = {&Var1, &Var2, &Var3}; /* Now each element in ptr array is a pointer to variable */ printf(‚%d‛, *ptr[0]); /* Will print 1*/ printf(‚%d‛, *ptr[1]); /* Will print 2*/ printf(‚%d‛, *ptr[2]); /* Will print 3*/ 29 Data types
  30. 30.  Derived Data Types (Pointers continue) We can have a pointer to pointer, which means that a pointer 2 holds the address of pointer 1, and pointer 1 points to any variable: long Var1 = 1; long int *ptr1 = &Var1; /* ptr1 points to Var1 */ long int **ptr2 = &ptr1; /* ptr2 points to ptr1 */ 30 Data types *ptr1 = 10; /* Will change Var1 value to 10 */ **ptr2 = 20; /* Will change Var1 value to 20 */ *ptr2 = NULL; /* Will change value of ptr1 to NULL */ Var1 = 1 Address 0xFF00 ptr1 = 0xFF00 Address 0xFFE4ptr2 = 0xFFE4 Address 0xFFF8 Var1 = 20 Address 0xFF00 ptr1 = NULL Address 0xFFE4ptr2 = 0xFFE4 Address 0xFFF8
  31. 31.  Derived Data Types (Pointers continue) We can make a pointer to function: A function pointer is a variable that stores the address of a function that can later be called through that function pointer. int myFunc(char myChar) { printf(‚Character is %c‛, myChar); return 0; } /* inside any code */ int (*ptr)(char) = myFunc; /* Now ptr holds the address of function myFunc */ ptr(‘A’); /* Will call myFunc which will print A*/ (*ptr)(‘A’); /* The same as previous line*/ 31 Data types
  32. 32.  Derived Data Types (Pointers continue) Pointers examples: what is the difference between all of this? int a; int *a; int **a; int a[10]; int *a[10]; int (*a)[10]; int (*a)(int); int (*a[10])(int); 32 Data types
  33. 33.  Derived Data Types (Pointers continue) Pointers examples: int a; /* An integer */ int *a; /* A pointer to an integer */ int **a; /* A pointer to a pointer to an integer */ int a[10]; /* An array of 10 integers */ int *a[10]; /* An array of 10 pointers to integers */ int (*a)[10]; /* A pointer to an array of 10 integers */ int (*a)(int); /* A pointer to a function a that takes an integer argument and returns an integer */ int (*a[10])(int); /* An array of 10 pointers to functions that take an integer argument and return an integer */ 33 Data types
  34. 34.  User Defined Data Types Sometimes, the basic set of data types defined in the C language such as int, float etc. may be insufficient for your application. In circumstances such as these, you can create your own data types which are based on the standard ones.  Structure: a kind of data structure that can store a collection of elements that are not of the same type. For example: struct carInfo { int speed; float batteryLevel; double kilometers; char bodyTemperature; }; This is called structure carInfo data type declaration, but till now there is no variable of that type exists. 34 Data types
  35. 35.  User Defined Data Types (Structure continue) To declare a variable of type carInfo: struct carInfo myCar; /* Uninitialized */ OR struct carInfo myCar = {0, 0, 0, 0}; /*Initialized*/ To edit any value of variable structure members: myCar.speed = readCarSpeed(); myCar.batteryLevel = readBatteryLevel(); myCar.kilometers = readCarKilometers(); myCar.bodyTemperature = readBodyTemperature(); 35 Data types
  36. 36.  User Defined Data Types (Structure continue) We can define a pointer to structure: struct carInfo *ptr = &myCar; /*ptr points to myCar*/ Editing structure members through pointer is achieved through arrow operator -> ptr->speed = readCarSpeed(); 36 Data types
  37. 37.  User Defined Data Types  Union: A union is a special data type available in C that allows to store different data types in the same memory location. You can define a union with many members, but only one member can contain a valid value at any given time. Unions provide an efficient way of using the same memory location for multiple-purpose: union U_data { int Var1; double Var2; char Var3; }; To declare a variable of type U_data : union U_data test; /* Uninitialized */ OR union U_data test = {0}; /*Initialized*/ 37 Data types
  38. 38.  User Defined Data Types (Union continue) Union members share the same memory location, so editing any member’s value will corrupt other members’ values: test.Var1 = 32; /* all other members are now 32*/ test.Var2 = 10;/* first 2 bytes of the union memory contains value 10, other 2 bytes are not changed*/ Size of Union variable test is the size of the biggest union member (double in our example). Using union saves memory, but a big caution should be taken to track which member contains valid data. 38 Data types
  39. 39.  User Defined Data Types  Enum: An enumeration is a user-defined data type consists of integral constants and each integral constant is give a name: enum week {sunday, monday, tuesday, wednesday, thursday, friday, saturday}; First element in enum by default takes value 0 if not initialized explicitly, so sunday will have value 0, monday will have value 1 and so on. Usage: enum week today; /*Declare variable of type enum week*/ today = Wednesday; /* today value now is 3*/ 39 Data types
  40. 40.  User Defined Data Types (Enum continue) enum members can be initialized with default values: enum week {sunday=5,monday,tuesday,Wednesday=9,thursday, friday, saturday}; Now sunday will have value 5, monday 6, tuesday 7, but wednesday will be 9 not 8, then thursday 10 and so on. Note: Multiple members can have the same value, but member name can’t exist twice. We can add boolean data type to C language using enum: enum boolean{ false, true}; enum boolean check; /* Now we can use check as boolean */ 40 Data types
  41. 41.  User Defined Data Types  typedef: We can define any data type we want from another data type using typdef keyword: typedef unsigned char uint8; typedef unsigned short uint16; typedef unsigned long uint32; typedef unsigned long long uint64; Now we can use the new defined data types: uint8 Var1; /*Var1 is unsigned char*/ 41 Data types
  42. 42.  User Defined Data Types (typedef continue) typedef with structure: typedef struct carInfo { unsigned long speed; unsigned short batteryLevel; unsigned long kilometers; unsigned char bodyTemperature; } Car_Type; Now instead of typing: struct carInfo myCar; We can type: Car_Type myCar; 42 Data types
  43. 43.  User Defined Data Types (typedef continue) We can simulate the behavior of typedef using #define like that: #define uint8 unsigned char uint8 Var1, Var2;/*Var1 and Var2 are now unsigned char*/ But the problem arises when trying something like that: typedef unsigned char* u8_ptr_Type; #define u8_ptr_Define unsigned char* u8_ptr_Type ptr1, ptr2; /* Both will be pointers */ u8_ptr_Define ptr3, ptr4; It is expected that both ptr3 and ptr4 are of type pointers to unsigned char, but because #define is just text replacement then what really happens is that previous line is replaced with: unsigned char* ptr3, ptr4; Which will make ptr3 successfully a pointer, but ptr4 will be normal variable. 43 Data types
  44. 44.  Declaration vs. Definition Declaration: A declaration tells the compiler the type of a variable, object or function, but doesn’t allocates memory for it or know all function details. extern uint8 Var1; /* Variable extern */ void myFunc(uint8 Var2); /* Function prototype */ This is particularly useful if you are working with multiple source files, and you need to use a function or variable in multiple files. You don't want to put the body of the function or same variable in multiple files statically, but you do need to provide a declaration for it. Then in only one file you can put the definition. 44 Data types
  45. 45.  Declaration vs. Definition Definition: A definition allocates memory for a variable or object and is the implementation of a function. uint8 Var1 = 10; void myFunc(uint8 Var2) /* Function Definition*/ { printf(‚myFunc definitionn‛); } Multiple declarations are allowed, but only one definition. 45 Data types
  46. 46.  Overflow vs. Underflow Overflow: It is the condition that occurs when a calculation produces a result that is greater in magnitude than the maximum value that a given register or storage location can store or represent. Unsigned overflow: unsigned char Var1 = 255; Var1++; /* Overflow */ printf(‚%d‛, Var1); /* Prints Zero */ Signed overflow: signed char Var2 = 127; Var2++; /* Overflow */ printf(‚%d‛, Var2); /* Prints -128*/ 46 Data types
  47. 47.  Overflow vs. Underflow Underflow: It is the condition that occurs when a calculation produces a result that is smaller than the minimum value that a can be stored. Unsigned underflow: unsigned char Var1 = 0; Var1 -= 8; /* Underflow */ printf(‚%d‛, Var1); /* Prints 248*/ Signed underflow: signed char Var2 = -128; Var2 -= 3; /* Underflow */ printf(‚%d‛, Var1); /* Prints 125*/ 47 Data types
  48. 48.  Type Casting Type casting is a way to convert a variable from one data type to another data type. For example, if you want to store a 'long' value into a simple integer then you can type cast 'long' to 'int'. Explicit casting: You can convert the values from one type to another explicitly using the cast operator as follows: (type_name) expression Example: int Var1 = 8, Var2 = 20; float Var3 = (float) Var2 / Var1; Implicit casting: Implicit casting doesn't require a casting operator. This casting is normally used when converting data from smaller integral types to larger or derived types to the base type: Var3 = Var2; /* Var2 value is implicitly converted to float and put inside Var3*/ 48 Data types
  49. 49. Data Type Qualifiers 49
  50. 50.  Data Type Qualifiers • Apart from the mentioned primitive data types, there are certain data type qualifiers that can be applied to them in order to alter their range and storage space and other properties to fit in various situations as per the requirement. • The data type qualifiers available in c are:  Size modifier: affects size (number of bytes): short int variable_name; /* 2 bytes integer */ long int variable_name; /* 4 bytes integer */  Sign modifier: affects values range: signed int variable_name; /* can have negative range */ unsigned int variable_name; /*can’t have negative range*/  Constant modifier: affects ability to change it during runtime: const char Var = ‘A’; /* can’t change at runtime */ 50 Data Type Qualifiers
  51. 51.  Primitive Data Types Summary with size and sign modifiers 51 Data type Size Value range char 1 -128 to 127 or 0 to 255 unsigned char 1 0 to 255 signed char 1 -128 to 127 int 2 or 4 -32,768 to 32,767 or -2,147,483,648 to 2,147,483,647 unsigned int 2 or 4 0 to 65,535 or 0 to 4,294,967,295 short 2 -32,768 to 32,767 unsigned short 2 0 to 65,535 long 4 -2,147,483,648 to 2,147,483,647 unsigned long 4 0 to 4,294,967,295 Data Type Qualifiers
  52. 52.  Primitive Data Types Summary with size and sign modifiers 52 Data type Size Value range Precision float 4 1.2E-38 to 3.4E+38 6 decimal places double 8 2.3E-308 to 1.7E+308 15 decimal places long double 10 3.4E-4932 to 1.1E+4932 19 decimal places Data Type Qualifiers
  53. 53.  Data Type Qualifiers It should be noted that the size and sign modifiers cannot be applied to float and can only be applied to integer and character data types.  Volatile modifier: tells the compiler that this variable can change outside of the code control (ex. HW Register value): volatile char Var = 10; if (Var == 10) { /* Any code*/ /* If variable was not declared volatile, the compiler may optimize this part by considering the condition always True and remove the if condition check ! */ } When a variable is declared volatile, any reference for that variable makes the compiler reload its value from RAM again to make sure it is the last updated version of that variable. 53 Data Type Qualifiers
  54. 54.  Data Type Qualifiers  Storage classes: A storage class defines the scope (visibility) and life-time of variables and/or functions within a C Program. o auto: The auto storage class is the default storage class for all local variables. int variable_name = 20; o register: The register storage class is used to define local variables that should be stored in a register instead of RAM. This means that the variable has a maximum size equal to the register size and can't have the unary '&' operator applied to it (as it does not have a memory location). /* Compiler will try to put this variable inside processor registers */ register int variable_name = 10; 54 Data Type Qualifiers
  55. 55.  Data Type Qualifiers  Storage classes: o static: The static storage class instructs the compiler to keep a local variable in existence during the life-time of the program instead of creating and destroying it each time it comes into and goes out of scope. Therefore, making local variables static allows them to maintain their values between function calls. The static modifier may also be applied to global variables or functions. When this is done, it causes that variable or function’s scope to be restricted to the file in which it is declared. o extern: The extern storage class is used to give a reference of a global variable or function that is visible to ALL the program files. When you use 'extern', the variable cannot be initialized and function can’t have a body however, it points to variable or function name that has been previously defined in another file. 55 Data Type Qualifiers
  56. 56.  Data Type Qualifiers  Storage classes: 56 File1.c static int variable_1 = 10; int variable_2 = 20; File2.c static int variable_1 = 50; extern int variable_2; /* Then inside any part of code */ printf(‚variable_1=%d, variable_2=%d‛, variable_1, variable_2); /* It will print variable_1=50, variable_2=20 */ Data Type Qualifiers
  57. 57. Scope and Lifetime 57
  58. 58.  Scope 58 Scope and Lifetime Local scope File scope Global scope
  59. 59.  Scope • The area of our program where we can actually access our variable is the scope of that variable.  Local scope: visible within function or statement block from point of declaration until the end of the block. void myFunc() { uint8 Var1 = 10; { uint8 Var2 = 11; printf(‚%d, %d‛, Var1, Var2); } printf(‚%d‛, Var2); } Var1 is visible inside all myFunc, but Var2 is only visible inside its inner block { } which is a local inner scope. 59 Scope and Lifetime
  60. 60.  Scope  File scope: visible within current file only, but its is visible to all functions inside this file, by using static keyword with variables or functions. 60 Scope and Lifetime File1.c static int Var1 = 50; void myFunc1() { printf(‚%d‛, Var1); } void myFunc2() { printf(‚%d‛, Var1); } File2.c void myFunc3() { printf(‚%d‛, Var1); } File3.c static int Var1 = 20; /* Same name, but totally different variable */
  61. 61.  Scope  Global scope: visible everywhere, it is defined in one file, and all other files can use it using extern keyword. 61 Scope and Lifetime File1.c int Var1 = 50; void myFunc1() { printf(‚%d‛, Var1); } void myFunc2() { printf(‚%d‛, Var1); } File2.c extern int Var1; void myFunc3() { printf(‚%d‛, Var1); }
  62. 62.  Lifetime • Life time of any variable is the time for which the particular variable outlives in memory during running of the program.  Automatic: An automatic variable has a lifetime that begins when program execution enters the function or statement block or compound and ends when execution leaves the function or block. Automatic variables are stored in a "function call stack". void myFunc() { uint8 Var1 = 10; { uint8 Var2 = 11; /* Var2 scope and life time ends here */ } /* Var1 scope and life time ends here */ } 62 Scope and Lifetime
  63. 63.  Lifetime  Dynamic: The lifetime of a dynamic data begins when memory is allocated (e.g., by a call to malloc()) and ends when memory is deallocated (e.g., by a call to free()). Dynamic data are stored in "the heap". /* Allocate 1000 bytes in RAM (Heap part) */ uint8 *myData = (uint8*) malloc(1000); /* Any code */ free(myData); /* Data is deallocated, so its life time ends here */ 63 Scope and Lifetime
  64. 64.  Lifetime  Static: A static variable is stored in the data segment of the "object file" of a program. Its lifetime is the entire duration of the program's execution. Static variable is initialized only the first time function is entered, and its value remains in memory between function calls. void myFunc() { static uint8 Var1 = 10; printf(‚%d‛, Var1); Var1++; } First call to myFunc will print 10, second call 11, third call 12 and so on. 64 Scope and Lifetime
  65. 65. Makefile introduction 65
  66. 66.  What is Make (GNU Make as an example) • GNU Make is a tool which controls the generation of executables and other non-source files of a program from the program's source files. • Make gets its knowledge of how to build your program from a file called the makefile, which lists each of the non-source files and how to compute it from other files. When you write a program, you should write a makefile for it, so that it is possible to use Make to build and install the program. 66 Makefile introduction
  67. 67.  Capabilities of Make • Make enables the end user to build and install your package (in case you provided them with source code) without knowing the details of how that is done because these details are recorded in the makefile that you supply. • Make figures out automatically which files it needs to update, based on which source files have changed. It also automatically determines the proper order for updating files, in case one non-source file depends on another non-source file. As a result, if you change a few source files and then run Make, it does not need to recompile all of your program. It updates only those non- source files that depend directly or indirectly on the source files that you changed. • Make is not limited to any particular language. For each non-source file in the program, the makefile specifies the shell commands to compute it. These shell commands can run a compiler to produce an object file, the linker to produce an executable, ar to update a library..etc. 67 Makefile introduction
  68. 68.  makefile structure • Make searches for a file called makefile without any extension to do its work. • Basic building elements of make file are:  Target: What is the output of current step.  Dependencies: What should be already available to start making current target.  Commands: What should be done to make current target. All of them are called “Rule”. A rule in the makefile tells Make how to execute a series of commands in order to build a target file from source files. It also specifies a list of dependencies of the target file. This list should include all files (whether source files or other targets) which are used as inputs to the commands in the rule. 68 Makefile introduction
  69. 69.  makefile structure • Rule structure: 69 Makefile introduction • Target name is typed at start of line, then followed by a colon : then followed by list of all dependencies that should be available to start executing commands, then on each new line write a new command to be executed, each command should have a Tab space before it or the make will not work. • Dependencies can be on one line, or can be divided on multiple lines: Target: dependency1 dependency2 ... commands ... Target: dependency1 dependency2 dependency3 ... commands ...
  70. 70.  makefile structure • Rule example: 70 Makefile introduction D:newProject>make file.o file.o: file.c gcc -c file.c -o file.o Create any C code file with the name file.c and put it in the same directory with the makefile. Then by running Make through command line: Create a text file with the name makefile without any extension and type the following inside it:  The Make utility will search for the file makefile, then it will search inside it for the required target file.o .  It will search what are file.o dependencies, it will find that it only depends on file.c.
  71. 71.  makefile structure  It will check “is the required dependency file.c available ?”, the Make file will find it in the same directory.  After the Make utility finds file.c, it will execute the commands under file.o target which is here compiling file.c, so the output of this command will be: 71 Makefile introduction D:newProject>make file.o gcc -c file.c -o file.o  Now you will find in the same directory a file created called file.o which is the compiled output file of file.c .
  72. 72.  makefile structure • If the Make didn’t find the required dependency file, it will search if this dependency is another required target then do it first: 72 Makefile introduction app.exe: file1.o file2.o gcc file1.o file2.o -o app.exe file1.o: file1.c gcc -c file1.c -o file1.o file2.o: file2.c gcc -c file2.c -o file2.o Create a C code file with the name file1.c which contains the main function, create another C code file file2.c with any code, and put them in the same directory with the makefile.
  73. 73.  makefile structure 73 Makefile introduction Run the Make and the output will be: D:newProject>make app.exe gcc -c file1.c -o file1.o gcc -c file2.c -o file2.o gcc file1.o file2.o -o app.exe Now you will find in the same directory a file called app.exe which is the compilation output.
  74. 74.  makefile structure • If the Make didn’t find the required dependency file in the directory or as any other target then it will generate this error and stop: 74 Makefile introduction D:newProject>make newFile.c make: *** No rule to make target `newFile.c'. Stop. • If Tab space doesn’t exist before any command then Make will generate this error and stop (note that 5 is number of first line containing error): D:newProject>make app.exe makefile:5: *** missing separator. Stop. • When you run Make, you can specify particular targets to update; otherwise, Make updates the first target listed in the makefile. Of course, any other target files needed as input for generating these targets must be updated first.
  75. 75.  makefile structure • Usually makefile contains 2 targets called all and clean to be used by any one without knowing the details of our makefile content: 75 Makefile introduction # all target is usually put as the first target all: app.exe clean: rm file1.o file2.o app.exe app.exe: file1.o file2.o gcc file1.o file2.o -o app.exe file1.o: file1.c gcc -c file1.c -o file1.o file2.o: file2.c gcc -c file2.c -o file2.o
  76. 76.  makefile structure • So typing all target will output the same result as typing app.exe, and typing clean target will delete file1.o, file2.o and app.exe . 76 Makefile introduction D:newProject>make clean rm file1.o file2.o app.exe • clean and all targets are called “Phony Targets”. A phony target is one that is not really the name of a file, rather it is just a name for a recipe to be executed when you make an explicit request, it may have dependencies like all target and may not have any dependencies like clean target.
  77. 77.  Make variables • A variable begins with a $ and is enclosed within parentheses (...) or braces {...}. Single character variables do not need the parentheses. Example: 77 Makefile introduction CC = gcc file1.o: file1.c $(CC) -c file1.c -o file1.o
  78. 78.  Make variables • Automatic Variables: Automatic variables are set by make after a rule is matched. They include: $@: the target filename. $*: the target filename without the file extension. $<: the first prerequisite filename. $^: the filenames of all the prerequisites, separated by spaces, discard duplicates. $+: similar to $^, but includes duplicates. $?: the names of all prerequisites that are newer than the target, separated by spaces. 78 Makefile introduction
  79. 79.  Make variables Example 1: 79 Makefile introduction file1.o: file1.c $(CC) -c $< -o $@ # It is the same as typing $(CC) -c file1.c -o file1.o Example 2: OBJ_FILES = file1.o file2.o app.exe: $(OBJ_FILES) $(CC) $^ -o $@ # It is the same as typing: # $(CC) file1.o file2.o -o app.exe
  80. 80.  Make variables Example 3: 80 Makefile introduction OBJ_FILES = file1.o file2.o LINK_TARGET = app.exe CLEAN_TARGET = $(LINK_TARGET) $(OBJ_FILES) all: $(LINK_TARGET) # It is the same as typing: # all: app.exe clean: rm $(CLEAN_TARGET) # It is the same as typing: # clean: rm file1.o file2.o app.exe
  81. 81.  Make implicit rules Implicit rules are a set of generalized instructions for doing certain tasks, where the instructions are provided as default. For example, an implicit rule can tell Make utility how to construct a .o file from a .c file without explicitly typing files names. Example 1: 81 Makefile introduction %.o: %.c $(CC) -c $< -o $@ # It means ‚whenever you find a required target with #any name but with extention .o and there is no #explicit rule for how to make this target, then #execute the next commands on this target.
  82. 82.  Make implicit rules Example 2: 82 Makefile introduction %.o: %.c $(CC) -c $< -o $@ file1.o: file1.c $(CC) -c file1.c -o explicit_file1.o app.exe: file1.o file2.o file3.o $(CC) $^ -o $@ # For file2.o and file3.o it will use the implicit #rule, so their output names will be file2.o and #file3.o but for file1.o it will find an explicit rule #for it then it will execute it, so its output name #will be explicit_file1.o
  83. 83.  Creating project hierarchy Most of the time the project contains different folders, not just all source and headers files in the same place, for example if we need to make one folder for source code files with name “src” and another folder for header files with name “inc”, then in the makefile we should do the following (assuming that the makefile is in the same directory as both folders): 83 Makefile introduction # Tell makefile Where to find source files vpath %.c ./src # Header files path INCLUDE_PATH = ./inc %.o: %.c $(CC) -c -I$(INCLUDE_PATH) $< -o $@ # -I$(INCLUDE_PATH) tells the compiler where to find #header files
  84. 84.  Header files issue After running make all, if we edited any source file the Make utility will recompile this file only and targets that depends on it. But if we changed any header file the make utility will not even notice and will not make any recompilation for the source code that depends on this file which we don’t want. This is because we didn’t tell the Make utility any thing about header files dependencies inside the makefile. That’s why we should make dependencies generation, which makes the compiler enters all source files, extract information about what header files that each source file depends on, generate a corresponding dependency file for this source file and include this information in our makefile. After making these steps, whenever we change a header file, any source file that depends on this header file will be recompiled and any target that depends on this source file will be made again. 84 Makefile introduction
  85. 85.  Header files issue (solution) 85 Makefile introduction # Generate dependencies list # Dependencies output path DEPS_PATH = ./dep/ # Source files path SRC_PATH = ./src/ # Header files path INCLUDE_PATH = ./inc/ # Get source files names with path SRC_FILES_ABSOLUTE = $(wildcard $(SRC_PATH)*.c) # Get source files names without path SRC_FILES = $(patsubst $(SRC_PATH)%.c,%.c,$(SRC_FILES_ABSOLUTE))
  86. 86.  Header files issue (solution) 86 Makefile introduction # Make dependencies names the same as sources names but with extension .d DEPS_FILES = $(patsubst %.c,%.d,$(SRC_FILES)) # Get dependencies files names with their path DEPS_FILES_ABSOLUTE = $(patsubst %.d,$(DEPS_PATH)%.d,$(DEPS_FILES)) # Rule to make dependencies files $(DEPS_PATH)%.d : %.c # Generate dependencies files (Small make file for each c file) $(CC) -MM -MP -MT$@ -I$(INCLUDE_PATH) $< -o $@
  87. 87.  Header files issue (solution) 87 Makefile introduction # Include all dependencies make files inside current make to be #used in #linking target to detect when any header file changes -include $(DEPS_FILES_ABSOLUTE) # Add dependencies to our target app.exe: $(OBJ_FILES) $(DEPS_FILES_ABSOLUTE) $(CC) $^ -o $@
  88. 88.  Make simple complete example Create 3 different folders: • src: contains all your source files. • inc: contains all your header files. • dep: empty, will be the output for dependencies files. Create empty text file with the name makefile without any extension and put the following inside the file: 88 Makefile introduction
  89. 89.  Make simple complete example 89 Makefile introduction # Tell makefile Where to find source files vpath %.c ./src # Compiler used CC = gcc # Dependencies output path DEPS_PATH = ./dep/ # Source files path SRC_PATH = ./src/ # Header files path INCLUDE_PATH = ./inc/
  90. 90.  Make simple complete example 90 Makefile introduction # What is our target name LINK_TARGET = app.exe # Objects used later to be linked to generate our link target OBJ = main.o Func1.o Func2.o Func3.o # What will be used at clean target CLEAN_TARGET = $(LINK_TARGET) $(OBJ) # Used with > make all all:$(LINK_TARGET) echo Bulding done !
  91. 91.  Make simple complete example 91 Makefile introduction # Used with > make clean clean: rm $(CLEAN_TARGET) echo Cleaning done ! # What will happen when requiring $(LINK_TARGET) $(LINK_TARGET): $(OBJ) $(DEPS_FILES_ABSOLUTE) $(CC) $(OBJ) -o $@ echo Linking done ! # What will happening when requiring any target with any file name with #extension .o %.o: %.c $(CC) -c -I$(INCLUDE_PATH) $< -o $@ echo File $< compilation done !
  92. 92.  Make simple complete example 92 Makefile introduction # Generate dependencies list # Get source files names with path SRC_FILES_ABSOLUTE = $(wildcard $(SRC_PATH)*.c) # Get source files names without path SRC_FILES = $(patsubst $(SRC_PATH)%.c,%.c,$(SRC_FILES_ABSOLUTE)) # Make dependencies names the same as sources names but with extension .d DEPS_FILES = $(patsubst %.c,%.d,$(SRC_FILES)) # Get dependencies files names with their path DEPS_FILES_ABSOLUTE = $(patsubst %.d,$(DEPS_PATH)%.d,$(DEPS_FILES))
  93. 93.  Make simple complete example 93 Makefile introduction # Rule to make dependencies files $(DEPS_PATH)%.d : %.c #Generate dependencies files (Small make file for each c file) $(CC) -MM -MP -MT$@ -I$(INCLUDE_PATH) $< -o $@ # Include all dependencies files inside current make to be used in #linking target to detect when any header file changes -include $(DEPS_FILES_ABSOLUTE)
  94. 94. Task 2 94
  95. 95. Mohamed AbdAllah Embedded Systems Engineer mohabdallah8@gmail.com 95

×