Portfolio Chapter 2
Portfolio Chapter 2
***********************************************************************************
*****
* Program: Predefined Preprocessor Macros Demonstration
*
* Description: Examples of using the predefined preprocessor macros in C,
* including to implement program logging.
*
* YouTube Lesson: https://www.youtube.com/watch?v=vIy0vEZpjtQ
*
* Author: Kevin Browne @ https://portfoliocourses.com
*
***********************************************************************************
****/
#include <stdio.h>
#include <stdlib.h>
int main()
{
// The __FILE__ predefined macro will be set to a string of the source code
// filename in which it is contained
printf("File: %s\n", __FILE__ );
// The __DATE__ predefined macro will be set to a string of the date at which
// the program was compiled
printf("Date: %s\n", __DATE__ );
// The __TIME__ predefined macro will be set to a string of the TIME at which
// the program was compiled
printf("Time: %s\n", __TIME__ );
// The __LINE__ predefined macro will be set to the line number of the source
// code file at which it appears
printf("Line: %d\n", __LINE__ );
// Create a log message 'task 1 done' that is stamped with this source code
// filename and the line number at which it occurs
logger("task 1 done", __FILE__, __LINE__);
// Create a log message 'task 2 done' that is also stamped with this source
// code filename and the DIFFERENT line number at which it occurs
logger("task 2 done", __FILE__, __LINE__);
return 0;
}
// Appends a log message string (msg) to a log file called log.txt that is
// "stamped" with the source code filename and line number at which it occurs.
void logger(char *msg, char *src, int line)
{
// File pointer variable to open the log file
FILE *file;
// Open the log file in 'append mode' so we can append additional log messages
// to the file... typically this is the mode we use when accessing a log file
// as we may run the program multiple times and want previous log messages
// to 'persist' in this log file rather than be overwritten.
file = fopen("log.txt", "a");
// If the log file failed to open exit with an error message and status.
if (file == NULL)
{
printf("Error opening log file.\n");
exit(1);
}
// Write the log message to the file, prepended with the source code filename
// AND the line number: source:line:msg format
fprintf(file, "%s:%d:%s\n", src, line, msg);
/*******************************************************************************
*
* Program: Function-like Preprocessor Macros
*
* Description: Examples of using function-like preprocessor macros in C, also
* known as parameterized macros or macros with arguments.
*
* YouTube Lesson: https://www.youtube.com/watch?v=4DS5E5tgxIA
*
* Author: Kevin Browne @ https://portfoliocourses.com
*
*******************************************************************************/
#include <stdio.h>
// What we call "object-like macros" are often used to define constant values in
// a program, such as a maximum value or PI
#define MAX 20
#define PI 3.14
// Macros can accept multiple parameters. One benefit of using macros compared
// to regular C functions is that macros can be "generic", so for example this
// macro will work with both ints and doubles as all that really occurs is
// text replacement!
#define area(base,height) 0.5 * base * height
// We can use a macro as an argument to another macro, so for example with the
// below function-like macro to find the min of two numbers, we could use it
// like: min(3,min(1,2)). We wrap the expression in () brackets to give it a
// higher order of evaluation. We do this because when the preprocessor
// expands macros we may get large expressions as a result, and we want the
// result of this macro to be evaluated FIRST before being used as part of a
// larger expression in order to get the desired behaviour.
#define min(x,y) ((x < y) ? x : y)
// We can use the # operator, sometimes called the stringizer operator, to turn
// any function-like macro argument into a string literal (e.g. s1). It will
// also concatenate the string with the adjacent string literal. We can also
// output a string using printf() in the usual way as we do with s2.
#define output(s1,s2) printf(#s1 " middle %s\n", s2);
// Prints a number 3 times. We make sure to evaluate the parameter using (num)
// before using the paramter, otherwise we may get unwanted behaviors if an
// expression is passed as a parameter to the function-like macro and that
// parameter is evaluated 3x (once inside each printf function call).
#define print_number_3x(num) ({ \
int nume = (num); \
printf("number: %d\n", nume); \
printf("number: %d\n", nume); \
printf("number: %d\n", nume); \
})
// Create a global variable number and a function which increments and returns
// the value of the number, such that we can demostrate a possible issue that
// may occur when a function-like macro argument is an expression.
int number = 0;
int increment()
{
number++;
return number;
}
int main()
{
// Print out the MAX constant value (i.e. object-like macro), the compiler
// will replace MAX with 20 during its first preprocessor phase, so the actual
// line of code that is truly compiled will be printf("Max: %d\b", 20);
printf("Max: %d\n", MAX);
// While it may seem unusual to have a line of code with no ; this will work
// because print is a macro and this will be replaced with a function call
// to printf() as defined above.
print
// Test out the inc() function-like macro, the code 'a = inc(a);' will be
// replaced with the code 'a = a + 1;' and a will be incremented.
int a = 2;
a = inc(a);
printf("a: %d\n", a);
// We can also use the macro with double variables and unlike a regular C
// function it will work fine! Because again text-replacement will occur,
// no function is ever truly called. This is an advantage of using
// function-like macros compared to regular C functions. Another advantage
// is a lack of overhead, i.e. extra work, from having to call a function as
// the computation is done with an inline expression, in this case
// '0.5 * base2 * height2', as opposed to having to call a function which
// involves creating and assigning values to local parameter variables
// and other work.
double base2 = 10.5;
double height2 = 5.2;
double area2 = area(base2, height2);
printf("area2: %f\n", area2);
// Use the result of a macro as an argument of another macro. You can see
// the expansion of macros by the preprocessor using the -E option in gcc,
// so for example: 'gcc -E -o function_like_macros function_like_macros.c'.
// The below statement will expand into a lengthy expression involving
// multiple ternary operators. It is essential that we wrapped the macro
// expression in ( ) when defining min to ensure that the resulting
// expressions are evaluated in the desired order, if we did not do this
// we may get bugs as a result.
int min_num = min(min(8,1),min(6,3));
printf("min_num: %d\n", min_num);
// The # operator in the output() function-like macro will turn "test1" (or
// any other parameter) into a string literal.
char string[] = "test2";
output(test1,string);
return 0;
}
/*******************************************************************************
*
* Program: #error Demonstration
*
* Description: Examples of using the #error preprocessor directive in C.
*
* YouTube Lesson: https://www.youtube.com/watch?v=sRMMyE3unZU
*
* Author: Kevin Browne @ https://portfoliocourses.com
*
*******************************************************************************/
#include <stdio.h>
// We can cause a compilation error to occur and provide a message with the
// #error directive like this:
#error ERROR MESSAGE GOES HERE!
int main()
{
return 0;
}
/
***********************************************************************************
*********8**********
NOTES:
In C, macros are essentially defined code snippets that are replaced with their
actual value or code during the preprocessing stage, before the program is
compiled. They are created using the #define preprocessor directive.
Benefits of Macros:
Code Reusability: Macros allow you to define a piece of code once and use it
multiple times throughout your program with a simple identifier. This reduces
redundancy and improves code readability.
Symbolic Constants: They can be used to define symbolic constants, making your code
more understandable and easier to maintain. Instead of using hardcoded values, you
can use meaningful names that represent the constant values.
Conditional Compilation: Macros can be used for conditional compilation based on
defined values. You can selectively include or exclude code blocks based on pre-
defined conditions.
Types of Macros:
Object-Like Macros: These are simple replacements for values or expressions. For
example:
#define PI 3.14159
#define MAX_VALUE 100
int radius = 5;
float area = PI * radius * radius; // Replaced with 3.14159 * 5 * 5 during
preprocessing
Function-Like Macros: These can take arguments and behave similarly to functions.
However, they lack some functionalities of true functions, like local variables and
return values.
Macro Substitution:
Predefined Macros: These built-in shortcuts come from standard libraries (e.g.,
<stdio.h>). Think of them as pre-written spells for common functions like printf or
constants like PI.
User-defined Macros: Craft your own spells with #define! Give a name (e.g., SQR)
and an expression (e.g., (x * x)) to be replaced throughout your code. Remember,
these are simple text replacements, so be mindful of order of operations and
potential side effects.
File Inclusion:
Predefined Headers: Standard libraries provide header files (.h files) like
<stdio.h> that contain declarations for functions and variables. Including these is
like summoning powerful libraries to your code.
User-defined Headers: Create your own header files to organize your project. Use
#include "myheader.h" to incorporate them within your source files. This promotes
code reusability and keeps things tidy.
Conditional Compilation:
The #if, #elif, and #else Spells: These directives act like conditional statements.
Based on defined macros or expressions, you can selectively include or exclude code
blocks. Imagine casting a spell that only activates if a certain condition is met!
Miscellaneous Tools:
The #pragma once directive is a compiler instruction used in C and C++ to ensure a
header file is included only once during compilation. While convenient, it's
important to note that #pragma once is not part of the official C/C++ standard and
might not work on all compilers.
It checks internally if it has already processed this header file during the
current compilation.
If not included before, the compiler processes the definitions and declarations as
usual.
If already included, the compiler skips the content within #pragma once,
effectively including the header only once.
The #error directive in C and C++ is used by the preprocessor to halt compilation
and display a user-defined error message.
1. Conditional Compilation Checks: In conjunction with preprocessor directives like
#ifdef and #ifndef, you can conditionally trigger #error based on defined macros or
missing header files.
#ifndef MATH_H
#error "Missing header file MATH_H. Please include it."
#endif
3. Internal Development Checks: You can use #error for internal development
purposes, like indicating unfinished code sections or reminding developers to
implement specific functionalities.
*/
#include stdio.h
#include conio.h
void function1();
void function2();
#pragma startup function1
#pragma exit function2
void main(){
printf("\n inside main function);
}
void function1(){
clrscr(); //because code starts from here
printf("\n inside function1");
}
void function2(){
printf("inside function 2");
getch(); //because code ends here
}
OUTPUT:
inside function1
inside maoin function
inside function2
/*
#pragma warn -rch
#pragma warn -par
#pragma warn -rvl
*/
#include<stdio.h>
#include<conio.h>
#pragma warn -par
#pragma warn-rch
#pragma warn -rvl
int f1();
void f2(int x);
int f3();
int main(){
clrscr();
f1();
f2(10);
f3();
getch();
}
int f1(){
printf("inside f1 func\n); //no integer returned
}
voidf2(intx){
printf("inside f2 function\n); //no operation done in x
}
int f3(){
printf("inside f3 function\n);
return 10;
printf("execution string"); // this string will not execute due to return
}
/* without pragmas-
18: warning: function should return a value
21: warning: parameter 'x' is never used
25: warning: unreachable code
26: warning: frunction should return a value
II) if we use a macro hundred times in a program, the macro expansion goes into our
source code at hundred different palaces, thus increasing the program size. On the
other hand, if a function is used, then even if it is called from hundred different
places in the program,it would take the same amount of space in the program, thus
making the prigram smaller and compact.
Preprocessor Operators
• The C preprocessor offers the following operators to help create
macros -
I)The Macro Continuation (1) Operator • A macro is normally confined to a single
line. The macro continuation operator (\) is used to continue a macro that is too
long for a single line.
• For example -
#define message for (a, b)\
printf(#a and " #b ": We love you! \n")
#include <stdio.h>
#define tokenpaster(n) printf ("token" #n" = %d", token##n)
int main(void) {
int token34 = 40;
tokenpaster (34);
return 0;
}
When the above code is compiled and executed, it produces the following result -
token34 = 40
It happened so because this example results in the following actual output from the
preprocessor -
printf ("token34 = %d", token34);
#endif
Output:
Compile Time Error: First include then compile
#pragma argsused:
This pragma suppresses the “unused parameter” warning for function parameters that
are not used within the function.
Example:
#include <stdio.h>
void myFunction(int x) {
#pragma argsused
// x is not used in this function
}
#pragma exit:
Specifies a function to run just before the program exits (after main() returns).
Example:
#include <stdio.h>
void cleanup() {
printf("Cleanup function called\n");
}
#pragma exit cleanup
int main() {
printf("Inside main()\n");
return 0;
}
#pragma inline:
Suggests that the compiler should inline a function (if possible).
Example:
#pragma inline
int add(int a, int b) {
return a + b;
#pragma option:
Used for compiler-specific options.
Example:
#pragma option -a1 // Set alignment to 1 bytes
#pragma warn:
Suppresses specific compiler warnings.
Example:
#pragma warn -rvl // Hide warnings about missing return value #pragma warn -par //
Hide warnings about unused function parameters
**********************************************************************************/