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

Class 3 - Input - Output, Buffering, Utilities

The document discusses input/output, buffering, and utilities in C programming. It provides examples of reading and writing files, changing buffer sizes, and using utilities like string and character functions. The examples demonstrate opening and reading multiple files, measuring performance with different buffer sizes, and converting a name between formats. Inconsistent performance when increasing the buffer size is likely due to larger read/write operations from the disk interfering with other processes.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
7 views

Class 3 - Input - Output, Buffering, Utilities

The document discusses input/output, buffering, and utilities in C programming. It provides examples of reading and writing files, changing buffer sizes, and using utilities like string and character functions. The examples demonstrate opening and reading multiple files, measuring performance with different buffer sizes, and converting a name between formats. Inconsistent performance when increasing the buffer size is likely due to larger read/write operations from the disk interfering with other processes.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 9

Class 3 - Input / Output, Buffering,

Utilities
In this class we will complete a C introduction by more detailed covering of input and output,
buffering, and miscellaneous utilities.

3.1 File Access


In examples so far we have read the standard input and written to the standard output. Both are
automatically defined for a program by the local operating system. The next step is to access a
file that is not already connected to a program. In our first example we will read the contents of a
file and direct it into standard output.

Example 3.1. Write a program that takes the file's name as a parameter and displays its
contents to standard output (screen).

#include <stdio.h>

int main(int argc, char * argv[]) {


FILE * fp;
int c;
fp = fopen(argv[1], "r");

while ((c = getc(fp)) != EOF) {


putc(c, stdout);
}
return 0;
}

Comments:

● The program accepts arguments from a command line


○ argc is the number of arguments
○ argv is an array of arguments where argv[0] is the program's name, argv[1]
is the first argument, etc.
● A file is represented by a pointer to a built-in FILE structure
● The file pointer is initialized by opening it (fopen) in a read mode
● It is then read one character at a time (getc) with each character output (putc) into a
built-in stdout file pointer
● Running on Linux
○ Create file vi 3_1.txt
○ gcc 3_1_read_file.c
○ ./a.out 3.1.txt

1
Which below will NOT cause a problem when running this program?

● Running it with no arguments


● No permission to access the file
● Running it with more than one argument
● Providing incorrect file's path

Answer: Running the program with more than one argument will not cause a problem provided
the first argument is correct.

In the next example, we will process multiple files.

Example 3.2. Write a program that takes multiple file names from the command line,
concatenates their contents and writes it out into standard output.

#include <stdio.h>

void file_copy(FILE * , FILE * );

int main(int argc, char * argv[]) {


FILE * fp;
char c;
if (argc == 1) {
printf("Need to specify at least 1 file\n");
return 1;
}
while (--argc > 0) {
if ((fp = fopen( * ++argv, "r")) == NULL) {
printf("Cannot open %s\n", * argv);
return 2;
}
file_copy(fp, stdout);
fclose(fp);
}
return 0;
}

/* Copy contents of one file into another */


void file_copy(FILE * fin, FILE * fout) {
char c;
while ((c = getc(fin)) != EOF) {
putc(c, fout);
}
}

Comments:

2
● Exceptions are handled by aborting the program with non-zero status
○ Note different return codes for incorrect number of arguments and failure to open
a file
● Both argc and argv are local variables for main and hence can be modified by the
program
● fclose closes the file pointer that is no longer needed, and releases this resource from
memory
● Operator --argc results in the counter argc decremented first, then being compared to
0
○ Note: the while loop results in (argc - 1) iterations

Which below is NOT correct after ++argv is called?

● *argv is the first element of a string


● argv points to an array's element
● argv is modified
● *argv is a pointer to the first element of a string

Answer: first option -- argv is a pointer to an element in an array. *argv dereferences it and
hence is an array's element, which itself is a pointer to the first element of a string. Note that
char ** argv is equivalent to char * argv[]. The former is passed to a function and hence
can be modified making the third option correct.

3.2 Buffering
The goal of buffering provided by the standard I/O library is to use the minimum number of read
and write calls. The library tries to do its buffering automatically for each I/O stream. Three types
of buffering are provided.

● Fully buffered
○ Actual I/O takes place when the standard I/O buffer is filled. The buffer is usually
obtained when the FILE pointer is allocated
○ The term flush describes the writing of a buffer.
● Line buffered
○ I/O is performed when a newline character is encountered on input or output. It is
typically used on a stream related to a terminal, such as standard input and
output.
● Unbuffered

3
○ The library does not buffer the characters.
○ For example, the standard error stream is usually unbuffered to display error
messages as quickly as possible.

In the next example we observe how buffering mode can be set and affect performance.

Example 3.3. Observe how changing the buffer size affects performance.

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#include <time.h>

#define LINE "........10........20........30........40........50\n"


#define MAX_LINES 100000

int main(int argc, char * argv[]) {


FILE * fp;
char line[strlen(LINE) + 1];
clock_t start;

if (argc != 3) {
printf("Usage a.out <file-path> <buffer-multiplier>\n");
return 1;
}

fp = fopen(argv[1], "w+");
if (fp == NULL) {
printf("Cannot open the file");
return 2;
}

char buf[atoi(argv[2]) * BUFSIZ];


setbuf(fp, buf);

/* Write out lines to a file */


start = clock();
for (int i = 1; i <= MAX_LINES; i++) {
fputs(LINE, fp);
}
fflush(fp);

/* Read all lines in the file */


rewind(fp);
int count = 0;
while (fgets(line, sizeof(line), fp)) {
count++;
}
fflush(fp);

4
printf("Written and read %d lines in %d ticks\n", count, clock() - start);

fclose(fp);
return 0;
}

Comments:

● The program creates and reads a file containing a predefined number of lines
○ The resulting file's size is ~ 5 MB
● It accepts the file's path and the standard buffer's size multiplier
○ Note the atoi function converting a string to an integer
● It uses setbuf function to override the standard buffer size of BUFSIZ
● The buffer is flushed at the write and read blocks
○ Note the use of fflush function
● Total file operations time is measures in clock ticks using the clock function

The following table shows file operations performance when changing the buffer size.

Buffer size multiplier Time in clock ticks

1 7028

2 6383

3 6209

4 6721

5 6982

10 6726

100 6063

As we can see performance improves initially, but then becomes less consistent. (Note: the
standard buffer size is 8K in these experiments)

What is the most likely reason for inconsistent performance improvements as the multiplier
increases?

● Fewer disk read/write operations


● More disk read/write operations
● Larger read/write operation's size from/to disk
● Smaller read/write operation's size from/to disk

5
Answer: as the buffer size increases a single operation to read/write from a disk to fill/release
the buffer becomes larger in size. This may be more likely to interfere with other processes on a
machine and become slower. Fewer read/write operations in theory should improve
performance. The second and fourth options are factually incorrect.

Are fflush statements necessary?

● Only the first one


● Only the second one
● Both
● Neither

Answer: only the first fflush is necessary to ensure that all data is written to the file. The read
operation will flush the data automatically.

3.3 Utilities
The standard library provides a wide variety of functions. We demonstrate a few of those by
examples.

Example 3.4. Convert a name from firstname lastname to Lastname, Firstname.

#include <stdio.h>

#include <string.h>

#include <ctype.h>

#define MAX_NAME 50

int main() {
char input[MAX_NAME];
char output[MAX_NAME];
char * p;

printf("Enter Firstname Lastname: ");


if (!fgets(input, MAX_NAME, stdin)) {
printf("Empty input\n");
return 1;
}

6
/* Remove newline */
p = strchr(input, '\n');
* p = '\0';

/* Find the last name separator (last space) */


p = strrchr(input, ' ');
*(p + 1) = toupper( * (p + 1));
strcpy(output, p + 1);
strcat(output, ", ");

/* First name is all until the separator */


* p = '\0';
input[0] = toupper(input[0]);
strcat(output, input);
printf("%s", output);
return 0;
}

Comments:

● We use fgets function to get the name from standard input


○ Recall that the input string has a newline character, which needs to be removed
● We use strchr and strrchr functions to find the first and the last occurrence of a
given character respectively
● Note that the input string is modified in place to copy its parts to output using strcpy
and strcat functions
● To convert a character to uppercase we use toupper function

Exercise 3.1. Improve this program by verifying that the input string is a name in the right format.
(Hint: utilize isalpha and other functions as needed.)

In the next example we utilize some math functions.

Example 3.5. Calculate future value of an initial investment that is reinvested each year into a
1-year CD.

The key here is a 1-year CD rate which is unknown in the future. For example, assume you
want to invest $100 for 2 years in one year:

● Year 1: $100 invested at 2%


● Year 2: $102 = $100 × 1. 02 invested at 7%
● Year 3: 109. 14 = $102 * 1. 07

Below is an implementation:

7
#include <stdio.h>

#include <stdlib.h>

#include <math.h>

#define LOWER_RATE 2
#define UPPER_RATE 10

float fv(float amount, float rate, unsigned int years);


float random_rate();
float fv_one_path(float amount, unsigned int years);

int main() {
float amount;
unsigned int years;
printf("Enter amount, years: ");
scanf("%f", & amount);
scanf("%u", & years);
printf("FV=$%.2f for amount=$%.2f, years=%d\n", fv_one_path(amount, years),
amount, years);
return 0;
}

/* Compute future value */


float fv(float amount, float rate, unsigned int years) {
return amount * pow(1 + rate, years);
}

/* Generate random rate */


float random_rate() {
return ((rand() % (UPPER_RATE - LOWER_RATE + 1)) + LOWER_RATE) / 100.0;
}

/* Run one path with random rates */


float fv_one_path(float amount, unsigned int years) {
float ret = amount;
for (int i = 1; i <= years; i++) {
float rate = random_rate();
ret = fv(ret, rate, 1);
printf("[DEBUG] rate:%.2f, year: %d, fv: %.2f\n", rate, i, ret);
}
return ret;
}

Comments:

● Function rand returns a random number between 0 and RAND_MAX


○ It is converted to a rate between realisting lower and upper numbers
● We assume a rate path, i.e. randomly generated future rates

What is the maximum precision of a rate returned by random_rate()?

8
● 3
● 2
● 1
● 0

Answer: 2. It first generates a random integer between 2 and 7, then divides it by 100 resulting
in numbers 0. 02, 0. 03, ...

Exercise 3.2. Modify the above program to compute an average future value over a given
number of paths.

You might also like