Part 10: C Input/Output and Memory Management with Examples

Part 10: C Input/Output and Memory Management with Examples

Input Output in C

In C programming, Input provides a set of built-in functions to read the given input and feed it to the program as per requirement. When we say Output, it means to display some data on screen, printer, or in any file.

The getchar() and putchar() Functions

The int getchar(void) function reads the next available character from the screen and returns it as an integer. This function reads only single character at a time. You can use this method in the loop in case you want to read more than one character from the screen.

The int putchar(int c) function puts the passed character on the screen and returns the same character. This function puts only single character at a time. You can use this method in the loop in case you want to display more than one character on the screen. Check the following example −

#include <stdio.h> 
int main( ) {
 int c;
 printf( "Enter a value :");
 c = getchar( ); 
printf( "\nYou entered: "); 
putchar( c ); 
return 0; 
}

When the above code is compiled and executed, it waits for you to input some text. When you enter a text and press enter, then the program proceeds and reads only a single character and displays it as follows −

$./a.out
Enter a value : this is test
You entered: t

The gets() and puts() Functions

The char *gets(char *s) function reads a line from stdin into the buffer pointed to by s until either a terminating newline or EOF (End of File).

The int puts(const char *s) function writes the string ‘s’ and ‘a’ trailing newline to stdout.

#include <stdio.h> 
int main( ) {
 char str[100]; 
printf( "Enter a value :");
 gets( str ); 
printf( "\nYou entered: "); 
puts( str ); 
return 0; 
}

When the above code is compiled and executed, it waits for you to input some text. When you enter a text and press enter, then the program proceeds and reads the complete line till end, and displays it as follows −

$./a.out
Enter a value : this is test
You entered: this is test

The scanf() and printf() Functions

The int scanf(const char *format, …) function reads the input from the standard input stream stdin and scans that input according to the format provided.

The int printf(const char *format, …) function writes the output to the standard output stream stdout and produces the output according to the format provided.

The format can be a simple constant string, but you can specify %s, %d, %c, %f, etc., to print or read strings, integer, character or float respectively. There are many other formatting options available which can be used based on requirements. Let us now proceed with a simple example to understand the concepts better −

#include <stdio.h>
int main( ) {

   char str[100];
   int i;

   printf( "Enter a value :");
   scanf("%s %d", str, &i);

   printf( "\nYou entered: %s %d ", str, i);

   return 0;
}
When the above code is compiled and executed, it waits for you to input some text. When you enter a text and press enter, then program proceeds and reads the input and displays it as follows −
$./a.out
Enter a value : seven 7
You entered: seven 7

It should be noted that scanf() expects input in the same format as you provided %s and %d, which means you have to provide valid inputs like “string integer”. If you provide “string string” or “integer integer”, then it will be assumed as wrong input. Secondly, while reading a string, scanf() stops reading as soon as it encounters a space, so “this is test” are three strings for scanf().

Type Casting in C

Converting one datatype into another is known as type casting or, type-conversion. For example, if you want to store a ‘long’ value into a simple integer then you can type cast ‘long’ to ‘int’. You can convert the values from one type to another explicitly using the cast operator as follows −

(type_name) expression

Consider the following example where the cast operator causes the division of one integer variable by another to be performed as a floating-point operation:

#include <stdio.h> 
main() { 
int sum = 17, count = 5;
 double mean;
 mean = (double) sum / count;
 printf("Value of mean : %f\n", mean );
 }

When the above code is compiled and executed, it produces the following result :

Value of mean : 3.400000

It should be noted here that the cast operator has precedence over division, so the value of sum is first converted to type double and finally it gets divided by count yielding a double value.

Type conversions can be implicit which is performed by the compiler automatically, or it can be specified explicitly through the use of the cast operator. It is considered good programming practice to use the cast operator whenever type conversions are necessary.

Memory Management in C

The C programming language provides several functions for memory allocation and management. These functions can be found in the <stdlib.h> header file.

Sr.No. Function & Description
1 void *calloc(int num, int size);

This function allocates an array of num elements each of which size in bytes will be size.

2 void free(void *address);

This function releases a block of memory block specified by address.

3 void *malloc(size_t size);

This function allocates an array of num bytes and leave them uninitialized.

4 void *realloc(void *address, int newsize);

This function re-allocates memory extending it upto newsize.

Allocating Memory Dynamically

While programming, if you are aware of the size of an array, then it is easy and you can define it as an array. For example, to store a name of any person, it can go up to a maximum of 100 characters, so you can define something as follows:

char name[100];

But now let us consider a situation where you have no idea about the length of the text you need to store, for example, you want to store a detailed description about a topic. Here we need to define a pointer to character without defining how much memory is required and later, based on requirement, we can allocate memory as shown in the below example:

#include <stdio.h>
 #include <stdlib.h> 
#include <string.h>
 int main() {
 char name[100]; 
char *description;
 strcpy(name, "Zara Ali"); 
/* allocate memory dynamically */ 
description = malloc( 200 * sizeof(char) );
 if( description == NULL ) {
 fprintf(stderr, "Error - unable to allocate required memory\n"); 
} else { 
strcpy( description, "Zara ali a DPS student in class 10th");
 }
 printf("Name = %s\n", name ); 
printf("Description: %s\n", description );
 }

When the above code is compiled and executed, it produces the following result.

Name = Zara Ali
Description: Zara ali a DPS student in class 10th

Same program can be written using calloc(); only thing is you need to replace malloc with calloc as follows:

calloc(200, sizeof(char));

So you have complete control and you can pass any size value while allocating memory, unlike arrays where once the size defined, you cannot change it.

Resizing and Releasing Memory

When your program comes out, operating system automatically release all the memory allocated by your program but as a good practice when you are not in need of memory anymore then you should release that memory by calling the function free().

Alternatively, you can increase or decrease the size of an allocated memory block by calling the function realloc(). Let us check the above program once again and make use of realloc() and free() functions −

#include <stdio.h>
 #include <stdlib.h>
#include <string.h> 
int main() { 
char name[100];
 char *description;
 strcpy(name, "Zara Ali"); /* allocate memory dynamically */
 description = malloc( 30 * sizeof(char) );
 if( description == NULL ) {
 fprintf(stderr, "Error - unable to allocate required memory\n"); 
} else {
 strcpy( description, "Zara ali a DPS student.");
 } /* suppose you want to store bigger description */ 
description = realloc( description, 100 * sizeof(char) ); 
if( description == NULL ) {
 fprintf(stderr, "Error - unable to allocate required memory\n"); 
} else { 
strcat( description, "She is in class 10th"); 
} 
printf("Name = %s\n", name ); 
printf("Description: %s\n", description ); 
/* release memory using free() function */ 
free(description);
 }

When the above code is compiled and executed, it produces the following result.

Name = Zara Ali
Description: Zara ali a DPS student.She is in class 10th

You can try the above example without re-allocating extra memory, and strcat() function will give an error due to lack of available memory in description.

 Part 11: Macros with File and Memory Management in Assemble Language

 Part 11: Macros with File and Memory Management in Assemble Language

Macros with File and Memory Management in Assemble Language

In Assembly macros is modular programming , file management concern with data input output standard and memory management convey the allocation of these data uses.

Macros use in Assembly

Macro is another way of ensuring modular programming in assembly language.It is a sequence of instructions, assigned by a name and could be used anywhere in the program.

  • In NASM, macros are defined with %macro and %endmacro directives.
  • The macro begins with the %macro directive and ends with the %endmacro directive.The Syntax for macro definition −
%macro macro_name  number_of_params

<macro body>

%endmacro

Where, number_of_params specifies the number parameters, macro_name specifies the name of the macro. The macro is invoked by using the macro name along with the necessary parameters. When you need to use some sequence of instructions many times in a program. For example, a very common need for programs is to write a string of characters in the screen. For displaying a string of characters, you need the following sequence of instructions:

mov       edx,len     ;message length

mov       ecx,msg     ;message to write

mov       ebx,1       ;file descriptor (stdout)

mov       eax,4       ;system call number (sys_write)

int           0x80        ;call kernel

In the above example of displaying a character string, the registers EAX, EBX, ECX and EDX have been used by the INT 80H function call. So, each time you need to display on screen, you need to save these registers on the stack, invoke INT 80H and then restore the original value of the registers from the stack. So, it could be useful to write two macros for saving and restoring data. Some instructions like IMUL, IDIV, INT, etc., need some of the information to be stored in some particular registers and even return values in some specific register(s). If the program was already using those registers for keeping important data, then the existing data from these registers should be saved in the stack and restored after the instruction is executed.

Example: There is an example shows defining and using macros:

; A macro with two parameters

; Implements the write system call

   %macro write_string 2

      mov   eax, 4

      mov   ebx, 1

      mov   ecx, %1

      mov   edx, %2

      int   80h

   %endmacro

section .text

   global _start            ;must be declared for using gcc               

_start:                     ;tell linker entry point

   write_string msg1, len1              

   write_string msg2, len2   

   write_string msg3, len3                

   mov eax,1                ;system call number (sys_exit)

   int 0x80                 ;call kernel

section .data

msg1 db               'Hello, programmers!',0xA,0xD

len1 equ $ - msg1                                         

msg2 db 'Welcome to the world of,', 0xA,0xD

len2 equ $- msg2

msg3 db 'Linux assembly programming! '

len3 equ $- msg3

 

OUTPUT:

Hello, programmers!

Welcome to the world of,

Linux assembly programming!

Assembly in File Management

The system considers any input or output data as stream of bytes. There are three standard file streams:

  • Standard input (stdin),
  • Standard output (stdout), and
  • Standard error (stderr).

File Descriptor

A file descriptor is a 16-bit integer assigned to a file as a file id. So, when a new file is created or an existing file is opened, the file descriptor is used for accessing the file. File descriptor of the standard file streams – stdin, stdout and stderr are 0, 1 and 2, respectively.

File Pointer

A file pointer specifies the location for a subsequent read/write operation in the file in terms of bytes. Each file is considered as a sequence of bytes. Each open file is associated with a file pointer that specifies an offset in bytes, relative to the beginning of the file. When a file is opened, the file pointer is set to zero.

File Handling System Calls

The following table briefly describes the system calls related to file handling:

%eax     Name    %ebx     %ecx     %edx

2              sys_fork               struct pt_regs    –              –

3              sys_read              unsigned int       char *    size_t

4              sys_write            unsigned int       const char *        size_t

5              sys_open            const char *        int           int

6              sys_close             unsigned int       –              –

8              sys_creat             const char *        int           –

19           sys_lseek            unsigned int       off_t      unsigned int

The steps required for using the system calls are same, as  discussed earlier:

Put the system call number in the EAX register.

Store the arguments to the system call in the registers EBX, ECX, etc.

Call the relevant interrupt (80h).

The result is usually returned in the EAX register.

Creating and Opening a File

For creating and opening a file, perform the following tasks −

Put the system call sys_creat() number 8, in the EAX register.

Put the filename in the EBX register.

Put the file permissions in the ECX register.

The system call returns the file descriptor of the created file in the EAX register, in case of error, the error code is in the EAX register.

Opening an Existing File

For opening an existing file, perform the following tasks:

Put the system call sys_open() number 5, in the EAX register.

Put the filename in the EBX register.

Put the file access mode in the ECX register.

Put the file permissions in the EDX register.

The system call returns the file descriptor of the created file in the EAX register, in case of error, the error code is in the EAX register.

Among the file access modes, most commonly used are: read-only (0), write-only (1), and read-write (2).

Reading from a File

For reading from a file, perform the following tasks:

Put the system call sys_read() number 3, in the EAX register.

Put the file descriptor in the EBX register.

Put the pointer to the input buffer in the ECX register.

Put the buffer size, i.e., the number of bytes to read, in the EDX register.

The system call returns the number of bytes read in the EAX register, in case of error, the error code is in the EAX register.

Writing to a File

For writing to a file, perform the following tasks −
Put the system call sys_write() number 4, in the EAX register.
Put the file descriptor in the EBX register.
Put the pointer to the output buffer in the ECX register.
Put the buffer size, i.e., the number of bytes to write, in the EDX register.
The system call returns the actual number of bytes written in the EAX register, in case of error, the error code is in the EAX register.

Closing a File

For closing a file, perform the following tasks:
Put the system call sys_close() number 6, in the EAX register.
Put the file descriptor in the EBX register.
The system call returns, in case of error, the error code in the EAX register.

Updating a File

For updating a file, perform the following tasks :
Put the system call sys_lseek () number 19, in the EAX register.
Put the file descriptor in the EBX register.
Put the offset value in the ECX register.
Put the reference position for the offset in the EDX register.
The reference position could be:
Beginning of file – value 0
Current position – value 1
End of file – value 2
The system call returns, in case of error, the error code in the EAX register.

Example: The following program creates and opens a file named myfile.txt, and writes a text ‘Welcome to Tutorials Point’ in this file. Next, the program reads from the file and stores the data into a buffer named info. Lastly, it displays the text as stored in info.

section .text

   global _start         ;must be declared for using gcc               

_start:                  ;tell linker entry point

   ;create the file

   mov  eax, 8

   mov  ebx, file_name

   mov  ecx, 0777        ;read, write and execute by all

   int  0x80             ;call kernel               

   mov [fd_out], eax   

   ; write into the file

   mov    edx,len          ;number of bytes

   mov    ecx, msg         ;message to write

   mov    ebx, [fd_out]    ;file descriptor

   mov    eax,4            ;system call number (sys_write)

   int       0x80             ;call kernel               

   ; close the file

   mov eax, 6

   mov ebx, [fd_out]   

   ; write the message indicating end of file write

   mov eax, 4

   mov ebx, 1

   mov ecx, msg_done

   mov edx, len_done

   int  0x80   

   ;open the file for reading

   mov eax, 5

   mov ebx, file_name

   mov ecx, 0             ;for read only access

   mov edx, 0777          ;read, write and execute by all

   int  0x80               

   mov  [fd_in], eax   

   ;read from file

   mov eax, 3

   mov ebx, [fd_in]

   mov ecx, info

   mov edx, 26

   int 0x80   

   ; close the file

   mov eax, 6

   mov ebx, [fd_in]

   int  0x80                  

   ; print the info

   mov eax, 4

   mov ebx, 1

   mov ecx, info

   mov edx, 26

   int 0x80      

   mov    eax,1             ;system call number (sys_exit)

   int       0x80              ;call kernel

section .data

file_name db 'myfile.txt'

msg db 'Welcome to draftsbook'

len equ  $-msg

msg_done db 'Written to file', 0xa

len_done equ $-msg_done

section .bss

fd_out resb 1

fd_in  resb 1

info resb  26

OUTPUT:

Written to file

Welcome to draftsbook

Memory Management

The sys_brk() system call is provided by the kernel, to allocate memory without the need of moving it later. This call allocates memory right behind the application image in the memory. This system function allows you to set the highest available address in the data section. This system call takes one parameter, which is the highest memory address needed to be set. This value is stored in the EBX register. In case of any error, sys_brk() returns -1 or returns the negative error code itself. The following example demonstrates dynamic memory allocation.

Example

The following program allocates 16kb of memory using the sys_brk() system call:

section .text

   global _start         ;must be declared for using gcc           

_start:                    ;tell linker entry point

   mov    eax, 45                  ;sys_brk

   xor      ebx, ebx

   int       80h

   add     eax, 16384           ;number of bytes to be reserved

   mov    ebx, eax

   mov    eax, 45                  ;sys_brk

   int       80h        

   cmp    eax, 0

   jl          exit        ;exit, if error

   mov    edi, eax ;EDI = highest available address

   sub     edi, 4                     ;pointing to the last DWORD 

   mov    ecx, 4096              ;number of DWORDs allocated

   xor      eax, eax               ;clear eax

   std                                      ;backward

   rep      stosd            ;repete for entire allocated area

   cld                                       ;put DF flag to normal state       

   mov    eax, 4

   mov    ebx, 1

   mov    ecx, msg

   mov    edx, len

   int       80h                         ;print a message

exit:

   mov    eax, 1

   xor      ebx, ebx

   int       80h               

section .data

msg       db           "Allocated 16 kb of memory!", 10

len     equ            $ - msg

OUTPUT:

Allocated 16 kb of memory!