0% found this document useful (0 votes)
10 views

Portfolio Chapter 2

Uploaded by

duttapratim515
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
10 views

Portfolio Chapter 2

Uploaded by

duttapratim515
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 11

/

***********************************************************************************
*****
* 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>

// When creating large programs made up of hundreds of source code files it


// may be useful to implement logging. A log function can be used by the
// program to write log messages to the log file at important points in its
// execution, to help trace the execution of the program after it runs if
// there is a bug for example. If these log messages contain the filename
// and line number at which they were written, this can help us to determine
// the part of our source code we need to examine to understand the bug.
// We'll implement a log function called 'logger' to do this!
void logger(char *msg, char *src, int line);

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__ );

// The __STDC__ predefined macro will be set to 1 if the compiler conforms to


// the ANSI C standard
printf("ANSI: %d\n", __STDC__ );

// 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);

// Close the log file as we are done working with it


fclose(file);
}

/*******************************************************************************
*
* 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 are really just a text-replacement operation done by the C compiler


// as part of its first preprocessor phase (there are several more phases). So
// for example we can replace the text "print" with a call to printf().
#define print printf("PRINT THIS!\n");
// We can also create "function-like macros" that accept and use parameters,
// here we add 1 to a parameter x. Really all that will occur is "text
// replacement" though, so if we pass in the variable a, inc(a) will be
// replaced with a + 1. It's important that there is no space between the
// macro name 'inc' and the beginning of the parameters (... otherwise we will
// have an error as it appears to be an object-like macro.
#define inc(x) x + 1

// 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);

// We can define a function-like macro across multiple lines by using ({ ... })


// and ending each line with the \ character. Here we find the largest element
// in an array. We use typeof() to find the type of elements in the array so
// that we can keep the function generic, such that it will work with both ints
// and doubles for example.
#define find_max(array,length) ({ \
typeof(array[0]) current_max = array[0]; \
for (int i = 1; i < length; i++) \
if (array[i] > current_max) \
current_max = array[i]; \
current_max; \
})

// 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);

// Test out the area() function-like macro, the code 'area(base1,height1)'


// will be replaced with the code '0.5 * base1 * height1' as per the macro
// definition, and the area of the triangle will be calculated.
int base1 = 20;
int height1 = 20;
int area1 = area(base1,height1);
printf("area1: %d\n", area1);

// 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);

// Test the find_max() function-like macro with an int array.


int int_array[] = {3,5,2,1,8,3,2};
int max_int = find_max(int_array,7);
printf("max_int: %d\n", max_int);

// Test the find_max() function-like macro with a double array.


double dbl_array[] = {9.3, 2.4, 10.2, 10.9};
double max_dbl = find_max(dbl_array, 4);
printf("max_dbl: %f\n", max_dbl);

// With a regular function in C, if we were to pass in num = increment() as an


// argument, the expression would first evaluate. The increment() function
// call would increment the global number variable to 1 and then return it,
// this value would be assigned to num, and the entire assignment = operator
// would evaluate to '1' which would then be passed to the function. But
// remember that function-like macros are essentially text replacement
// operations, and we will not get this behaviour!
//
// Instead the expression num = increment() will be used in function-like
// macro, potentially evaluating the expression multiple times as it is used
// in the macro (e.g. evaluating 3x in each call to printf() in the
// print_number_3x macro). If this were to occur, we could print out the
// numbers 1,2,3 instead of the number 1 three times!
//
// Because we evalute the parameter 'num = increment()' in the print_number_3x
// function-like macro *before* we use it in the macro, we will get the number
// 1 output three times as desired.
int num;
print_number_3x( num = increment() );

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>

// The limits.h library includes an constant INT_MAX defining the largest


// value an int can store... this may be different from one system to the next.
#include <limits.h>

// We can cause a compilation error to occur and provide a message with the
// #error directive like this:
#error ERROR MESSAGE GOES HERE!

// If our program depends on some condition in order to function correctly


// and that condition is verifiable by preprocessor directives at compile-time,
// we can use #error to cause a compilation error when that condition is NOT
// met to prevent our program from compiling but functioning incorrectly. For
// example here we cause a compilation error to occur if INT_MAX does not
// exceed a threshold value.
#if INT_MAX < 2147483648
#error INT_MAX Not Large Enough!
#endif

int main()
{

return 0;
}

/
***********************************************************************************
*********8**********
NOTES:

#include directive is a preprocessor command that instructs the compiler to include


the contents of a specified header file at the point where the directive appears in
your code.

How the Preprocessor Works:

1. Encountering #include: When the compiler encounters an #include directive in


your code, it pauses the compilation of the current source file.
2. Locating the Header File: The compiler searches for the specified header file
based on predefined locations (system directories for standard headers) or the
current directory for user-defined headers.
3. Including Content: Once found, the entire content of the header file is inserted
verbatim into your source code at the location of the #include directive.
4. Resuming Compilation: After including the header content, the compiler resumes
processing the main source file as if the included code was originally written
there.

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.

#define SQR(x) ((x) * (x))


# include <stdio.h>
int main(){
int result = SQR(4); // Replaced with (4 * 4) during preprocessing
printf("%d", result);
return 0;
}

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:

Pragmas: These are compiler-specific directives (denoted by #pragma) that provide


additional instructions to the compiler. Think of them as whispers to the compiler,
influencing optimization or warnings.

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

2. Enforcing Compiler Requirements: You can use #error to check if specific


compiler features or versions are met before continuing. For example:
#if _cplusplus < 201103L // Check for C++11 support
#error This code requires C++11 or later.
#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.

#error is a hard stop. Compilation immediately halts after encountering it.


The error message should be clear and informative, guiding the developer towards
resolving the issue.

*/

// demonstrating preprocessor directives

/* some pragma directives


#paragma startup
#pragma warn
#pragma exit
#pragma inline
#pragma option
*/

#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

Notes from bytexl:

The various preprocessor directives are as follows:


1) Macro Expansion (#define)
II)File Inclusion(#include)
III)Conditional Compilation(#ifdef -#endif) IV)Miscellaneous Directives
(#undef,#pragma-startup, exit, warn)

Difference between Macro & Function


I)In a macro call, the preprocessor replaces the macro template with its macro
exapansion only.whereas, in a functional call the control is passed to a function
along with certain arguments, some calculations are performed in the function& a
useful value is returned back from the
function.

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")

II)The Stringize (#) Operator


• The stringize or number-sign operator ('#'), when used within a macro definition,
converts a macro parameter into a string constant.
This operator may be used only in a macro having a specified argument or parameter
list.
• For example -
# define say(m) printf(#m)
void main()
{
clrscr();
say(Hello);
getche();
}
Output:
Hello

III) The Token Pasting (##) Operator


• The token-pasting operator (##) within a macro definition combines two
arguments.
• It permits two separate tokens in the macro definition to be joined into a single
token.
• For example -

#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);

// Example to demonstrate ifndef pragma


#include<stdio.h>
#ifndef _MATH_H
#error First include then compile
#else
void main(){
float a;
a=sqrt(7);
printf("%f",a);
}

#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

**********************************************************************************/

You might also like