Tag: programming

Compiling a C program. Various stages involved.

C is a high level programming language which converts a C code to executable which can be run in the respective target OS environment.

Usually a C program code is written with an extension .c. An IDE or editor is used to add code to the programs. Once done a compiler is used to compile the program code to executable. Some IDE provide built-in compiler while others provide option to select the compiler and debugger.

Steps involved in creating and executable.

  1. Add code using an editor.
  2. Compile using a compiler to create the executable binary. Linux uses gcc and windows uses Microsoft visual studio integrated compiler cl. There are other compilers available in multiple OS environments like borland C compiler.
  3. Fix any coding issues and move to step 1. Otherwise go to step 4.
  4. Run the final executable.
  5. Fix and run time found issues and move to step 1. Otherwise go to step 6.
  6. Final program ready.

The compilation phase can be split into 4.

  1. Pre-processing phase.
  2. Compilation phase.
  3. Assembly phase.
  4. Linking phase.

Pre-processing phase includes the following sub phases. This will generate filename.s file(in linux) which does have the below steps completed.

  • Removal of Comments.
  • Expansion of Macros.
  • Expansion of the included files.
  • Conditional compilation.

Compilation phase takes the pre-processed file and convert to assembly instructions. In case of linux it will generate the GNU assembly code.

Assembly phase include converting the assembly code into object code. filename.s will be converted to filename.o in Linux. This file will contain machine level instruction.

Linking phase is the final phase where the static libraries are linked as well as symbol lookup done for the dynamic libraries which are going to be loaded as part of run time load. The linker also add the extra code to call the main function as well as handling the exit from the program.

Note:- gcc uses dynamic linking for the standard C library functions like printf.

 

Source: Compiling a C program:- Behind the Scenes – GeeksforGeeks

Use -save-temps gcc option to generate the intermediate files.
gcc -Wall -save-temps -o sample -L .  -I . application.c -lcall_lib

yogi@192 0830]$ ls application.*
application.c application.i application.o application.s

application.c – program code.

application.i – preprocessed file.

application.s – assembly file

application.o – object file with machine instructions.

sample – Final program.

Quick summary on shared libraries

Shared libraries are reusable libraries which can be used by multiple applications at the same time. The advantages of shared libraries are

  1. Can be loaded at application start or on demand at run time.
  2. Can be upgraded separately and reloaded.

Only disadvantage compared to statically linked library is, there is an overhead for loading the library and resolving the symbols at run time.

In case of loading during application start, the loader will load the library into process virtual address space and resolve before the application is launched.

In case of loading dynamically the same can be achieved with the respective system library apis when in need..

Windows it will be a dynamically linked library(*.dll) and uses the same PE format as windows executable.

Linux it will be a shared object(*.so) and uses the ELF format as a normal linux executable.

In linux use the -shared gcc option to create a shared object. if your library name provided is abc.so, the linux linker will create it as libabc.so.

While statically linking, the option -labc should be provided to link with the same.

If you are using dynamic linking, you need to use dlopen(3) ,dlsym(3) and dlclose(3) apis to load, access the symbol and unload the library.

Sample codes

application.c

#include <stdio.h>
#include “call_lib.h”

int main(int argc, char * argv[])
{
printf(“In application\n”);
call_lib(3);

return 0;
}

call_lib.h

int call_lib(int num);

library.c

#include <stdio.h>
#include “call_lib.h”

int call_lib(int num)
{
printf(“passed value is %d\n”, num);

return 0;
}

To compile the library code
gcc -shared -fPIC -o library.so library.c

To compile the application code
gcc -o sample -L /home/yogi/libs -lrary application.c

Running the executable

set first the library path

$export LD_LIBRARY_PATH=/home/yogi/libs:$LD_LIBRARY_PATH
$ ./sample
In application
passed value is 3

Source: Working with Shared Libraries | Set 1 – GeeksforGeeks

Assertions in C/C++

Assertions help programmers to test conditions and abort the programs. Mainly used when the code cannot execute further if the condition is not met.

The syntax of assert is

void assert(scalar expression);

Need to include the following header which defined the
assert macro.

#include <assert.h>

Assert prints the error message to standard error
and terminates the program by calling abort(3).

code

assert( val == 0)

output

prog: some_file.c:16: some_func: Assertion `val == 0' failed.

Assertions can be turned off by defining NDEBUG macro. Adding NDEBUG will cause assert not to generate any code to print error and abort.

Recommendation is to not use the NDEBUG macro if using assert to detect error conditions as software may behave non-deterministically.

Source: Assertions in C/C++ – GeeksforGeeks

Single instance in Linux applications

Sometimes you may need to write an application in lnux for which multiple instances are not allowed. There is a simple way to achieve even though you have lot of other ways like semaphores, sockets etc to achieve the same. Here is a small snippet code to achieve the same using file locking.

#include <sys/file.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

 

int pid_file = -1; int rc = -1;

pid_file = open(“/var/run/whatever.pid”, O_CREAT | O_RDWR, 0666);

rc = flock(pid_file, LOCK_EX | LOCK_NB);

if(rc) {

if(EWOULDBLOCK == errno)     {

close(pid_file); // another instance is running

return;

}

}

else {

// this is the first instance

}

 

Writing OS and compiler independent code

My experience in programming was mixed between Linux and Windows. There are lot of standards like POSIX to standardize the api’s and there behavior across different OS and compiler environment. Here in this article I’m going to provide few tips which will be helpful for you to write OS and compiler independent code. Send me mail(yogi@yogindar.com) or add comment if you want any specific tips to be included in this section which you believe it is commonly encountered.

Few principles to be kept in mind before writing a code is

1. Reusable code
Never assume your code will be used only in Linux or Windows or for the same reason the code is written. There will be cross compiled projects which may have the same requirement. E.g:- An embedded Linux project adding a new encryption algorithm which is reused in windows based application.

2. Use standards
Use standards like ANSII C, POSIX where ever applicable as the compilers C compilation and C library will be supporting those standards by default. There will be other standards which will support more identifiers and compiler specific macros and I suggest you should be avoiding those.

3. Avoid compiler supported macros and keywords.
Most of the time you may have to compile your code based on the best compiler suited for that OS environment. You might be tempted to use those short cuts. Unless and until it is extremely important avoid those keywords and macros.

4. OS specific includes
All OS/compiler specific includes, #defines should be defined in OS specific files. This way while porting you may have to only touch this source file and not any other source files. Include platform.h in all your source code. Obviously Makefile differs.
E.g:-
Have a file named platform.h
Inside platform.h have the following code.
#ifdef WINDOWS
#include "windowsdef.h"
#endif
#ifdef LINUX
#include "linuxdef.h"
#endif

Now you will think what you will define within windowsdef.h and linuxdef.h. Next few points covers the same.

5. Segregating the includes
Identify all the #includes you need for your code. Include your OS specific #includes accordingly.
E.g:- a specific type may be defined in two different header files/path between windows and Linux.

6. Typedefining
There will be OS specific types which needs to be manually added in the other OS specific header file.
E.g:- uint16_t in Linux GCC.
I suggest a better approach for the same. Have a platform independent Typedef.
E.g:-
In windows
typedef unsigned short UINT16
In Linux
typedef uint16_t UINT16
Ones you have made the type independent start using the type independent code only.

7. Library codes
Similar to types and includes you may find differences in system and C library calls. I suggest you should add a separate platform specific library to handle the scenario. In windows have a seperate code for oswin.lib/oswin.dll where you define OS/compiler specific library calls for windows. Similarly you can do the same for Linux too with a liboslinux.a/liboslinus.so.

8. C library calls.
There will be lot of C library calls with the following behavior.
a. Same between both the OS. Until and unless you have to port to a new OS/compiler/different C library, you dont have to worry. If you encounter the same situation just add the specific prototype in #include os(newos).h and define the C library call in os(newos).lib/os(newos).so.
b. Minimal difference. May be the parameters used are swapped. Use #define in your one of the OS specific include file(oswin.h/oslinux.h) and there by call the corresponding C library call for that OS. Avoid rewriting the same C library code. If you have the code why do you want to rewrite. our time is valuable for lot of other things so never reinvent.
c. Completely different. As mentioned din point a where a new OS does not have the code understand the api parameters, return codes well and also the behaviour and rewrite the same with your OS specific library(oswin.lib/oslinux.a)

9. System calls
I suggest the same C library points applies here. So copied the same and also added a specific section more.
a. Same between both the OS. Until and unless you have to port to a new OS/compiler/different system call, you dont have to worry. If you encounter the same situation just add the specific prototype in #include os(newos).h and define the C library call in os(newos).lib/os(newos).so.
b. Minimal difference. May be the parameters used are swapped. Use #define in your one of the OS specific include file(oswin.h/oslinux.h) and there by call the corresponding C library call for that OS. Avoid rewriting the same C library system call code. If you have the code why do you want to rewrite. our time is valuable for lot of other things so never reinvent.
c. Completely different. As mentioned din point a where a new OS does not have the code understand the api parameters, return codes well and also the behaviour and rewrite the same with your OS specific library(oswin.lib/oslinux.a)
d. Very OS specific system calls. E.g:- fork(). There is no equivalent system call in windows. I suggest write wrappers and make use of the application of fork() rather than its oS specific behavior. Use it for creating a child process but with wrappers.

10. Threads, IPC and sockets
Most of them are written based on POSIX and windows and Linux will have the same implementation with same api name, different api name, different library support etc. Find out those differences and apply the above mentioned logic.

11. Wrappers/Stubs
Try using wrappers and stubs. This will help the code easy for porting and maintenance.
Wrapper is an api defined by us over the C library call(system call). Like typedef we are trying to consolidate same names for functions also there by we know what are C library calls we use and we can quickly know what are api’s to be defined by us.
Stubs is just a dummy api. Sometimes we may need those stubs because of the implementation of library calls.
E.g:- In windows socket calls are defined in a separate library and in Linux they are system calls and part of GNU C library. To use the socket libraries we need to use WSAInit() in windows for which we can define an equivalent stub in Linux and a wrapper in windows named socketinit().

12. Coding styles
I don’t have a strong opinion on using Hungarian notation or Linux kernel coding style. Use one of them but don’t mix and match.

13. Avoid using architecture specific code. One example is using asm keyword. Between 320-bit windows itself there is a difference in using assembly code inline.

14. Other common tips which is not OS specific.
a. Ensure your structures are padded correctly.
b. Take care of endianism if you are sending/receiving data to a different device/computer.
c. Avoid architecture specific code. If architecture suggests the local variables are initialized to 0 by default never rely on the same. Initialize all the local variables.
d. Use common indentation style. 1 tab = 4 spaces.
e. Avoid mix and match of new line characters. In windows it is \r\n, Linux editors it is \n and in apple editors it is \r. Use always one environment for the same. Preferably windows.
f. Use the source code management system property to take care of new lines correctly.

I believe I almost covered all the points required to write a OS/compiler independent code. Keep it as a practice and even if you have to work over a project which already has written OS/compiler dependent code try using the methodology I have given. Im sure your code will be reused by somebody with less effort if you implement the same coding methodology.

Printing the gcc predefined macros to stdout

Any time you wanted to know the list of gcc used macros and their values. Here is the command details

touch foo.h; gcc -dM -E foo.h

will list all the macros used.

The option

-dM will generate the list of all #defines used by the pre-processor during execution, this also includes pre-defined macros.

-E will stop the compiler after pre-processing stage. Will not enter to the compiling stage. The output will be in the preprocessed form and will be sent to the stdout.

Page 2 of 2
1 2