Chapter 5¶
Exercise 5-1¶
main.c
#include <stdio.h>
#include <ctype.h>
/* return 1 on success, 0 if not a number and EOF on end of file */
int getint(int* pn);
int main()
{
int res, n;
do{
printf("> ");
res = getint(&n);
switch (res){
case 0:
printf("Not an integer!\n");
break;
case 1:
printf("Integer: %d\n", n);
break;
case EOF:
printf("End Of File!\n");
break;
}
}while (res > 0);
return 0;
}
int getint(int* pn)
{
int c;
/* remove trailing spaces */
while (isspace(c = getc(stdin)));
/* return if end of file or not a number */
if (c == EOF) {
return EOF;
}
if (!isdigit(c) && c != '+' && c != '-') {
ungetc(c, stdin);
return 0;
}
/* sign handling */
int sign = (c == '-') ? -1 : 1;
if (c == '-' || c == '+'){
c = getc(stdin);
}
if (!isdigit(c)) {
ungetc(c, stdin);
return 0;
}
/* number computation */
*pn = 0;
while(isdigit(c)){
*pn = *pn * 10 + c - '0';
c = getc(stdin);
}
*pn *= sign;
ungetc(c, stdin);
return 1;
}
Compilation and run:
$ gcc main.c
$ ./a.out
> 3214
Number: 3214
> -2145
Number: -2145
> Hello
Not a number!
Notes:
To keep familiarizing ourselves with the C standard library, we use
getc
andungetc
from<stdio.h>
instead ofgetch
andungetch
defined in Exercises 4-3, 4-4, 4-5, 4-6. One difference between them is that the ones from the standard library needs an additional argument to specify where the input comes from (stdin
in this case). The solutions of exercises of Chapter 7 are all about input and output.
ungetc
may fail to push somec
values. Specifically, if the value ofc
equals that of the macroEOF
, the operation fails and the input stream is unchanged. This behaviour is alright, so we do not protectungetc
from receivingEOF
and we also do not check forungetc
errors.
Exercise 5-2¶
main.c
#include <stdio.h>
#include <ctype.h>
int getfloat(float* pn);
int main()
{
int res;
float n;
do{
printf("> ");
res = getfloat(&n);
switch (res){
case 0:
printf("Not a number!\n");
break;
case 1:
printf("Number: %f\n", n);
break;
case EOF:
printf("End Of File!\n");
break;
}
}while (res > 0);
return 0;
}
int getfloat(float* pn)
{
int c, sign;
/* remove trailing spaces */
while (isspace(c = getc(stdin)));
/* return if end of file or not a number */
if (c == EOF) {
return EOF;
}
if (!isdigit(c) && c != '+' && c != '-' && c != '.') {
ungetc(c, stdin);
return 0;
}
/* sign handling */
sign = (c == '-') ? -1 : 1;
if (c == '-' || c == '+'){
c = getc(stdin);
}
if (!isdigit(c) && c!='.') {
ungetc(c, stdin);
return 0;
}
/* capture number value before '.' */
int isnum = 0;
*pn = 0;
while(isdigit(c)){
isnum = 1;
*pn = *pn * 10 + c - '0';
c = getc(stdin);
}
/*capture numbervalue after '.' */
if (c == '.') {
int power = 1;
c = getc(stdin);
while (isdigit(c)) {
isnum = 1;
power *= 10;
*pn = *pn * 10 + c - '0';
c = getc(stdin);
}
*pn /= power;
}
*pn *= sign;
ungetc(c, stdin);
return isnum;
}
Compilation and run:
$ gcc main.c
$ ./a.out
> 1424
Number: 1424.000000
> -12.134
Number: -12.134000
> .124
Number: 0.124000
> 123.
Number: 123.000000
> .
Not a number!
Notes:
Same considerations as Exercise 5-1.
Exercise 5-3¶
main.c
#include <stdio.h>
void mystrcat(char* s, const char* t)
{
while (*s++ != '\0');
s--;
while (*s++ = *t++);
}
int main()
{
char *s, *t;
long unsigned ns, nt;
int ls, lt;
s = t = NULL;
ns = nt = 0;
while ((ls = getline(&s, &ns, stdin)) > 0){
s[ls-1] = '\0'; /* remove trailing '\n' from string s */
if ((lt = getline(&t, &nt, stdin)) <= 0){
return 0;
}
if (ns < ls + lt +1){
printf("error: %ld characters allocated for s, we need %d \n", ns, ls + lt + 1);
return 1;
}
mystrcat(s, t);
printf("%s\n", s);
}
return 0;
}
Compilation and run:
$ gcc main.c
$ ./a.out
I saw her duck
for cover
I saw her duck for cover
Notes:
We change the name of
strcat
tomystrcat
because there is a previous declaration ofstrcat
in<stdio>
.It is responsibility of the caller to check that
s
has enough space to concatenate the stringt
at its end.
Exercise 5-4¶
main.c
#include <stdio.h>
int strend(char* s, char* t)
{
char *sstart = s;
char *tstart = t;
/* get pointer to the end */
while (*s++ != '\0');
while (*t++ != '\0');
--s;
--t;
/* iterate from back to end */
while (*s-- == *t-- && s >= sstart && t >= tstart);
return (t < tstart) ? 1 : 0;
}
int main()
{
char *s, *t;
long unsigned ns, nt;
int ls, lt;
s = t = NULL;
ns = nt = 0;
while ((ls = getline(&s, &ns, stdin)) > 0){
if ((lt = getline(&t, &nt, stdin)) <= 0){
return 0;
}
printf("match: %d\n", strend(s, t));
}
return 0;
}
Compilation and run:
$ gcc main.c
$ ./a.out
The way to start
to start
match: 1
this is not real
unreal
match: 0
Notes:
We change the name of
strcat
tomystrcat
because there is a previous declaration ofstrcat
in<stdio>
.It is responsibility of the caller to check that
s
has enough space to concatenate the stringt
at its end.
Exercise 5-5¶
main.c
#include <stdio.h>
/*
* Copies the string pointed to by t, including the terminating
* null byte ('\0'), to the buffer pointed to by s.
* The strings may not overlap.
* At most n bytes of src are copied.
* Warning: If there is no null byte among the first n bytes of src,
* the string placed in dest will not be null-terminated.
* It returns a pointer to the destination string s.
*/
char* strncpy(char* s, const char* t, long unsigned n)
{
char* save_s = s;
while (n-- > 0 && (*s++ = *t++));
return save_s;
}
/*
* The function appends the t string to the s string,
* overwriting the terminating null byte ('\0') at the end of s,
* and then adds a terminating null byte.
* The strings may not overlap.
* The function will use at most n bytes from t and
* t does not need to be null-terminated if it contains n or more bytes.
* The resulting string in dest is always null-terminated.
* The function return a pointer to the resulting string s.
*/
char* strncat(char* s, const char* t, long unsigned n)
{
char* save_s = s;
while (*s++ != '\0');
s--;
while(n-- > 0 && (*s++ = *t++));
*(--s) = '\0';
return save_s;
}
/*
* The strcmp() function compares the compares
* the first (at most) n bytes of s1 and s2.
* It returns an integer indicating the result of the comparison:
* It returns 0 if they are equal.
* It returns a negative value if s is less t.
* It returns a positive value if s is greater t.
*/
int strncmp(const char* s, const char* t, long unsigned n)
{
while(*s++ == *t++ && *s != '\0');
return *(s) - *(t);
}
int main()
{
char *s, *t;
long unsigned ns, nt;
int ls, lt;
s = t = NULL;
ns = nt = 0;
while ((ls = getline(&s, &ns, stdin)) > 0){
if ((lt = getline(&t, &nt, stdin)) <= 0){
return 0;
}
s[ls-1] = '\0'; /* remove trailing '\n' from string s */
t[lt-1] = '\0'; /* remove trailing '\n' from string t */
printf("Comparison result: %d\n", strncmp(s, t, ls));
strncat(s, t, ns - ls);
printf("Concatenation result: %s\n", s);
strncpy(s, t, ns - ls);
printf("Copy results: %s\n", s);
}
return 0;
}
Compilation and run:
$ gcc main.c
$ ./a.out
abcda
abcda
Comparison result: 0
Concatenation result: abcdaabcda
Copy results: abcda
Notes:
The description of the functions has been taken from the man pages.
Exercise 5-6¶
main.c
#include <stdio.h>
#include <ctype.h>
#define MAXLINE 80
#define CINTLEN 20
#define V_LEN 6
enum {NUMBER};
enum {POSITIVE, NEGATIVE};
int mygetline(char *s, int n);
int atoi(const char* nptr);
void itoa(int n, char *str);
void reverse(char *str);
int strindex(char *s, char *t);
int getop(char *s);
int main()
{
char s[MAXLINE], t[MAXLINE], number[MAXLINE];
int slen, tlen;
int n;
while (1){
// read a line
do{
printf("> ");
}while((slen = mygetline(s, MAXLINE)) == 0);
if (slen == -1){
return -1;
}
// read a 2nd line
do{
printf("> ");
}while((tlen = mygetline(t, MAXLINE)) == 0);
if (tlen == -1){
return -1;
}
// test functions
printf("%03d: %s\n", slen, s);
reverse(s);
printf("rev: %s\n", s);
reverse(s);
printf("int: %d\n", n = atoi(s));
itoa(n, number);
printf("str: %s\n", number);
printf("pos: %d\n", strindex(s, t));
// read an operator (and discard rest of the line)
printf("> ");
getop(s);
mygetline(t, MAXLINE);
printf("ope: %s\n", s);
}
return 0;
}
/*
* NON STANDARD
* read a line from stdin and store it in buffer `s` (whitout '\n')
* The total line is readed but at most `n` characters
* are stored in `s` including the terminating '\0'.
* The total length of the line readed is returned.
*/
int mygetline(char *s, int n)
{
int c, res;
char *save_s = s;
char *send = s + n;
while(s < send - 2 && (c = getchar()) != EOF && c != '\n') {
*s++ = c;
}
*s++ = '\0';
res = s - save_s - 1;
while (c != EOF && c != '\n'){
c = getchar();
++res;
}
return res;
}
/*
* STANDARD
* The atoi() function converts the initial portion of the string
* pointed to by `nptr` to int.
*/
int atoi(const char* nptr)
{
// capture sign
int sign = (*nptr == '-') ? -1 : 1;
if (*nptr == '-' || *nptr == '+'){
++nptr;
}
// capture num values
int n=0;
while (*nptr != '\0' && isdigit(*nptr)) {
n = n * 10 + *nptr - '0';
++nptr;
}
return sign * n;
}
/*
* NON STANDARD
* The itoa() function converts an integer `n` to a
* null-terminated string and stores the result in the array
* given by `str` parameter.
* If the value is negative, the resulting string is preceded
* with a minus sign (-).
*/
void itoa(int n, char *str)
{
int sign;
unsigned un;
char *save_str = str;
un = (unsigned) n;
sign = n < 0 ? NEGATIVE : POSITIVE;
if (n < 0){
un = ~un + 1;
}
do{
*str++ = un % 10 + '0';
} while ((un /= 10) > 0);
if (sign == NEGATIVE){
*str++ = '-';
}
*str = '\0';
reverse(save_str);
}
/*
* NON STANDARD
* The reverse() function reverse the sequence of characters
* in string `str` ('\n' will also get reversed!)
*/
void reverse(char *str)
{
char *pend;
pend = str;
while (*pend != '\0'){
++pend;
}
--pend;
char c;
while (str < pend){
c = *str;
*str++ = *pend;
*pend-- = c;
}
}
/* return index of t in s, -1 if none */
int strindex(char *s, char *t)
{
char *s_base, *t_base;
char *s2;
s_base = s;
t_base = t;
while (*s != '\0'){
s2 = s;
t = t_base;
while (*s2 == *t && *t != '\0'){
++s2;
++t;
}
if (*t == '\0'){
return s-s_base;
}
++s;
}
return -1;
}
/* get next operator or numeric operand */
int getop(char *s)
{
int c;
while (isblank(*s = c = getchar()));
*++s = '\0';
if (!isdigit(c) && c != '.'){
return c; // not a number
}
if (isdigit(c)){ // collect integer part
while (isdigit(*s++ = c = getchar()));
}
if (c == '.'){ // collect decimal part
while (isdigit(*s++ = c = getchar()));
}
*--s = '\0';
ungetc(c, stdin);
return NUMBER;
}
Compilation and run:
$ gcc main.c
$ ./a.out
> 123.25 Hello there!
> there
019: 123.25 Hello there!
rev: !ereht olleH 52.321
int: 123
str: 123
pos: 13
> 123.25
ope: 123.25
Notes:
mygetline
is a rewrite the one from Exercise 1-16 but it does not store the ending\n
char in the buffer.atoi
is a rewrite of the one defined in the standard library.itoa
andreverse
are rewrites of the ones from Exercise 3-4.strindex
is a rewrite of the one defined in K&R page 69.getop
is a rewrite of the one defined in K&R page 78.
Exercise 5-7¶
main.c
#include <stdio.h>
#include <time.h>
#define MAXLINES 32768
#define LINESIZE 128
int readlines_dynamic(char **lineptr, int maxlines);
int readlines_static(char (*lines)[LINESIZE], int maxlines);
int mygetline(char *s, int n);
int main(int argn, char **argv)
{
char mode;
int nlines;
clock_t tic, toc;
char* lineptr[MAXLINES];
char lines[MAXLINES][LINESIZE];
printf("mode clocktics nº lines\n");
printf("---- --------- --------\n");
tic = clock();
nlines = readlines_static(lines, MAXLINES);
toc = clock();
printf(" sta %9ld %8d\n", toc - tic, nlines);
tic = clock();
nlines = readlines_dynamic(lineptr, MAXLINES);
toc = clock();
printf(" dyn %9ld %8d\n", toc - tic, nlines);
return 0;
}
int readlines_dynamic(char **lineptr, int maxlines)
{
char *line;
int len, linecount;
long unsigned n;
linecount = 0;
while(maxlines-- > 0){
n = 0;
line = NULL;
if (len = getline(&line, &n, stdin) < 1){
break;
}
*lineptr++ = line;
++linecount;
}
return linecount;
}
int readlines_static(char (*lines)[LINESIZE], int maxlines)
{
int len, n, linecount;
linecount = 0;
while(maxlines-- > 0 && (len = mygetline(*lines++, LINESIZE)) > 0){
++linecount;
}
return linecount;
}
/*
* NON STANDARD
* read a line from stdin and store it in buffer `s` (whitout '\n')
* The total line is readed but at most `n` characters
* are stored in `s` including the terminating '\0'.
* The total length of the line readed is returned.
*/
int mygetline(char *s, int n)
{
int c, res;
char *save_s = s;
char *send = s + n;
while(s < send - 2 && (c = getchar()) != EOF && c != '\n') {
*s++ = c;
}
*s++ = '\0';
res = s - save_s - 1;
while (c != EOF && c != '\n'){
c = getchar();
++res;
}
return res;
}
The text input is from sampletext.txt, we use redirection (< sampletext.txt
)
to use the contents of this file as standard input when executing the program.
Compilation and run:
$ gcc main.c
$ ./a.out < sampletext.txt
mode clocktics nº lines
---- --------- --------
sta 5028 32768
dyn 3061 32768
Notes:
mygetline
is the same as the one in Exercise 5-6.We use
getline
from standard library to fill an array of pointer. We usemygetline
to fill an array of arrays. The former case is ~1.6 faster, this implies thatgetline
from standard library is more efficient thanmygetline
, even when it allocates memory dynamically.Notice the difference in the first argument of
readline_static
andreadline_dynamic
:
char **lineptr
is used to pass an array of pointers.
char (*lines)[LINESIZE]
is used to pass an array of arrays.The parentheses in
char (*lines)[LINESIZE]
are neccesary becausechar *lines[LINESIZE]
is equivalent tochar *lines[]
, which is just the same aschar **lines
(an array of pointers).
Exercise 5-8¶
main.c
#include <stdio.h>
static char dtab[2][13] = {
{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
int day_of_year(int year, int month, int day);
int month_day(int year, int yday, int* month, int* day);
int main()
{
int yearday;
int year, month, day;
while(1){
printf("> year month day: ");
if (scanf("%d %d %d", &year, &month, &day) != 3){
return -1;
}
if ((yearday = day_of_year(year, month, day)) == -1){
fprintf(stderr, "error day_of_year: wrong input\n");
return -1;
}
printf("day of year: %d\n", yearday);
printf("> year yearday: ");
if (scanf("%d %d", &year, &yearday) != 2){
return -1;
}
if (month_day(year, yearday, &month, &day) == -1){
fprintf(stderr, "error month_day: wrong input\n");
return -1;
}
printf("date: %02d/%02d/%d\n", day, month, year);
}
return 0;
}
/* return `day of year` on success and -1 on error */
int day_of_year(int year, int month, int day)
{
int leap = year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
// error checking
if (day < 1 || month < 1 || month > 12 || day > dtab[leap][month]){
return -1;
}
// continue
while (--month > 0){
day += dtab[leap][month];
}
return day;
}
/* return 0 on success and -1 on error */
int month_day(int year, int yday, int* month, int* day)
{
int leap = year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
// error checking
if (yday < 1 || yday > ((leap) ? 366 : 365)){
return -1;
}
// continue
int local_month = 0;
while (yday > dtab[leap][local_month]) {
yday -= dtab[leap][local_month];
++local_month;
}
*month = local_month;
*day = yday;
return 0;
}
Compilation and run:
$ gcc main.c
$ ./a.out
> year month day: 1991 6 19
day of year: 170
> year yearday: 1991 170
date: 19/06/1991
> year month day: 2023 13 5
error day_of_year: wrong input
Exercise 5-9¶
main.c
#include <stdio.h>
static char dtab[2][13] = {
{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
int day_of_year(int year, int month, int day);
int month_day(int year, int yday, int* month, int* day);
int main()
{
int yearday;
int year, month, day;
while(1){
printf("> year month day: ");
if (scanf("%d %d %d", &year, &month, &day) != 3){
return -1;
}
if ((yearday = day_of_year(year, month, day)) == -1){
fprintf(stderr, "error day_of_year: wrong input\n");
return -1;
}
printf("day of year: %d\n", yearday);
printf("> year yearday: ");
if (scanf("%d %d", &year, &yearday) != 2){
return -1;
}
if (month_day(year, yearday, &month, &day) == -1){
fprintf(stderr, "error month_day: wrong input\n");
return -1;
}
printf("date: %02d/%02d/%d\n", day, month, year);
}
return 0;
}
/* return `day of year` on success and -1 on error */
int day_of_year(int year, int month, int day)
{
int leap = year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
// error checking
if (day < 1 || month < 1 || month > 12 || day > *(*(dtab+leap)+month)){
return -1;
}
// continue
while (--month > 0){
day += *(*(dtab+leap)+month);
}
return day;
}
/* return 0 on success and -1 on error */
int month_day(int year, int yday, int* month, int* day)
{
int leap = year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
// error checking
if (yday < 1 || yday > ((leap) ? 366 : 365)){
return -1;
}
// continue
int local_month = 0;
while (yday > *(*(dtab+leap)+local_month)) {
yday -= *(*(dtab+leap)+local_month);
++local_month;
}
*month = local_month;
*day = yday;
return 0;
}
Compilation and run:
$ gcc main.c
$ ./a.out
> year month day: 1991 6 19
day of year: 170
> year yearday: 1991 170
date: 19/06/1991
> year yearday: 2025 400
error month_day: wrong input
Notes:
The solution is exactly the same as Exercise 5-8 but we replace every subscriptor
[]
operator knowing that:a[b]is equivalent to:
*(a + b)
Exercise 5-9¶
main.c
#include <stdio.h>
static char dtab[2][13] = {
{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
int day_of_year(int year, int month, int day);
int month_day(int year, int yday, int* month, int* day);
int main()
{
int yearday;
int year, month, day;
while(1){
printf("> year month day: ");
if (scanf("%d %d %d", &year, &month, &day) != 3){
return -1;
}
if ((yearday = day_of_year(year, month, day)) == -1){
fprintf(stderr, "error day_of_year: wrong input\n");
return -1;
}
printf("day of year: %d\n", yearday);
printf("> year yearday: ");
if (scanf("%d %d", &year, &yearday) != 2){
return -1;
}
if (month_day(year, yearday, &month, &day) == -1){
fprintf(stderr, "error month_day: wrong input\n");
return -1;
}
printf("date: %02d/%02d/%d\n", day, month, year);
}
return 0;
}
/* return `day of year` on success and -1 on error */
int day_of_year(int year, int month, int day)
{
int leap = year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
// error checking
if (day < 1 || month < 1 || month > 12 || day > dtab[leap][month]){
return -1;
}
// continue
while (--month > 0){
day += dtab[leap][month];
}
return day;
}
/* return 0 on success and -1 on error */
int month_day(int year, int yday, int* month, int* day)
{
int leap = year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
// error checking
if (yday < 1 || yday > ((leap) ? 366 : 365)){
return -1;
}
// continue
int local_month = 0;
while (yday > dtab[leap][local_month]) {
yday -= dtab[leap][local_month];
++local_month;
}
*month = local_month;
*day = yday;
return 0;
}
Compilation and run:
$ gcc main.c
$ ./a.out
> year month day: 1991 6 19
day of year: 170
> year yearday: 1991 170
date: 19/06/1991
> year month day: 2023 13 5
error day_of_year: wrong input
Exercise 5-10¶
main.c
#include "stack.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <math.h>
static void usage_error(void);
static int isnumber(const char *s);
int main(int argc, char* argv[])
{
if(argc < 2){
usage_error();
}
double op1, op2;
while (--argc > 0){
if (isnumber(*++argv)){ // number
push(atof(*argv));
}else{ // operator
if (strlen(*argv) != 1){
usage_error();
}
op2 = pop();
op1 = pop();
switch(**argv){
case('+'):
push(op1 + op2);
break;
case('-'):
push(op1 - op2);
break;
case('*'):
push(op1 * op2);
break;
case('/'):
push(op1 / op2);
break;
}
}
}
// only one number must remain on stack
double res;
res = pop();
if (!isnan(pop())){
usage_error();
}
printf("%.2f\n", res);
return 0;
}
/* error function helper */
static void usage_error(void)
{
fprintf(stderr, "error. Usage: expr <numbers and operators>\n");
exit(-1);
}
/* return 1 if s represent a number or 0 if not */
static int isnumber(const char *s)
{
int i;
/* get sign */
i = 0;
if ((s[0] == '+' || s[0] == '-') && s[1] != '\0'){
++i;
}
/* get all digits including '.' */
while(isdigit(s[i])){
++i;
}
if (s[i] == '.'){
++i;
}
while(isdigit(s[i])){
++i;
}
/* check and return */
if (i > 0 && s[i] == '\0'){
return 1;
}
return 0;
}
stack.h
/* return number on success and NAN on error */
double pop(void);
/* return 0 on success and -1 on error */
int push(double x);
stack.c
#include "stack.h"
#include <math.h>
#define MAXSTACK 128
static double stack[MAXSTACK];
static int rsp = 0; /* next free position of the stack */
double pop(void)
{
if (rsp == 0){
return NAN;
}
return stack[--rsp];
}
int push(double x)
{
if (rsp >= MAXSTACK){
return -1;
}
stack[rsp++] = x;
return 0;
}
Compilation and run:
$ gcc -o expr main.c stack.c
$ ./expr 2 3 4 + '*'
Notes:
stack.h and stack.c are the same as Exercises 4-3, 4-4, 4-5, 4-6 but with restricted functionality (only
push
andpop
).
isnumber
is the same as the one defined in Exercises 4-3, 4-4, 4-5, 4-6, but we make use of theconst
qualifier to indicate that its argument is read-only.In Linux shell,
*
is a glob that refer to all the files in the current directory. Thus we need to use quotes'*'
to indicate the*
asterisk character as a command-line argument.Notice also when running any executable file we preceed the file with
./
, this is done to indicate to the shell that the program to execute is in the current directory and it is NOT a built-in command or an executable file within some folder in a directory of the PATH environmental variable.
Note
The most common implementation to pass command-line arguments
and environmental variables to a program when it begin executing,
is to use main
arguments and/or the external environmental
variable environ
.
main
is defined as follow:
int main(int argc, char **argv, char **envp)
where both argv
and envp
are both
NULL terminated arrays of pointer to strings.
argv
has the command-line argument values
(argc
just indicates its length) and
envp
has the environmental variables.
We can also access the environmental variables strings
through the external global variable environ
wich is also
a null-terminated array of strings
with the environmental variables:
extern char **environ;
environ
has the advantage over envp
that there is a set of
functions from the standard library for reading and modifying the
environment: getenv
, setenv
and unsetenv
.
Nowadays, environ
is the recommended way to access and edit
environmental variables in a program. Moreover, while this is
widely supported:
int main(int argc, char **argv, char **envp)
it is not guarantee to be present by the C99 standard or POSIX. While this one:
int main(int argc, char **argv)
is defined in the C99 standard.
Exercise 5-11, 5-12¶
main.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAXLINE 128
#define TABLEN 8
int settab(int argc, const char *const *argv, char *tabs, int n);
int extendtab(int start, int tabsize, char *tabs, int n);
int istabstop(int pos, const char *tabs, int n);
int entab(int argc, const char *const *argv, const char *tabs, int n);
int detab(int argc, const char *const *argv, const char *tabs, int n);
int main(int argc, const char *const *argv)
{
// set tabs
char tabs[MAXLINE];
if (settab(argc, argv, tabs, MAXLINE) != 0){
fprintf(stderr, "error settab: wrong command-line arguments\n");
return -1;
};
// run program depending on the executable file name
if (strstr(*argv, "entab")){
return entab(argc, argv, tabs, MAXLINE);
}
if (strstr(*argv, "detab")){
return detab(argc, argv, tabs, MAXLINE);
}
fprintf(stderr, "unrecognized program name: %s\n", *argv);
return -1;
}
int entab(int argc, const char *const *argv, const char *tabs, int n)
{
int c;
int nb = 0;
int pos = 0;
while ((c = getchar()) != EOF){
// reset blank spaces if current position is a tabstop
if (istabstop(pos, tabs, n) && nb > 0){
nb = 0;
putchar('\t');
}
// handle character and update position
if (c == ' '){
++nb;
++pos;
}else if (c == '\t'){
putchar('\t');
nb = 0;
do{
++pos;
}while(!istabstop(pos, tabs, n));
}else{
while(nb > 0){
putchar(' ');
--nb;
}
putchar(c);
++pos;
}
// reset position if newline
if (c == '\n'){
pos = 0;
}
}
return 0;
}
int detab(int argc, const char *const *argv, const char *tabs, int n)
{
int c;
int pos = 0;
while ((c = getchar()) != EOF) {
if (c == '\t') {
do{
putchar(' ');
++pos;
}while (!istabstop(pos, tabs, MAXLINE));
}else{
putchar(c);
++pos;
}
if (c == '\n') {
pos = 0;
}
}
return 0;
}
/*
* return 1 if 'pos' is a tabstop and 0 if not.
*/
int istabstop(int pos, const char *tabs, int n)
{
if (pos < 0 || pos >= n){
return 1;
}
return tabs[pos];
}
/*
* settab() uses a list of command-line arguments 'argc' and 'argv'
* wich integer strings that indicate stops to fill
* the buffer 'tabs' with has length of at least 'size'.
* 'tabs' is filled with 0s in every position but the ones indicates
* in 'argv', which are filled with 1s.
* return 0 on success and -1 on error
*/
int settab(int argc, const char *const *argv, char *tabs, int size)
{
// prepopulate tabs with 0s
int i;
for (i = 0; i < size; ++i){
tabs[i] = 0;
}
// get -m +n command-line arguments
int m, n;
m = 0; n = TABLEN; // default behaviour
for (i = 0; i < 2 && --argc > 0; ++i){
++argv;
if(**argv == '-'){
m = atoi(*argv + 1);
}else if (**argv == '+'){
n = atoi(*argv + 1);
}else{
--argv;
++argc;
break;
}
}
// use -m +n command-line argument to update stops in tabs
if (extendtab(m, n, tabs, size) == -1){
return -1;
}
// use rest of command-line argument to update stops in tabs
int pos;
int maxpos = 0;
while (--argc > 0){
pos = atoi(*++argv);
if (pos <= 0){
return -1;
}
if (pos < size){
tabs[pos] = 1;
}
if (pos > maxpos){
maxpos = pos;
}
}
return 0;
}
/*
* extendtab() add tab stops to 'tabs'
* every 'tabsize' columns, starting at column 'start'.
* return 0 on success and -1 on error
*/
int extendtab(int start, int tabsize, char *tabs, int n)
{
int i;
if (tabsize <= 0){
return -1;
}
i = start;
while( i < n){
tabs[i] = 1;
i += tabsize;
}
return 0;
}
Compilation and run:
$ gcc -o detab main.c
$ gcc -o entab main.c
$ ./detab
This line has spaces and tabs intercalated but the output will have only spaces.
This line has spaces and tabs intercalated but the output will have only spaces.
$ ./entab
this line has lots of spaces and tabs but the output have only the minimum tab necessary.
this line has lots of spaces and tabs but the output have only the minimum tab necessary.
Notes:
The first string in
argv
is the name of the executable file used to run the program. Thus by compiling the program with 2 different names entab and detab we can have different behaviour.
Note
From this exercise onwards, we are going to give a proper name to the executable file when compiling (instead of leaving the default a.out)
Exercise 5-13¶
main.c
#include <stdio.h>
#include <stdlib.h>
#define DEFAULT 10
#define MAXLINES 100
int main(int argc, char **argv)
{
int i;
if (argc > 2){
fprintf(stderr, "Usage: ./tail -n\n");
return -1;
}
int n = DEFAULT;
if (argc == 2){
if (**++argv != '-'){
fprintf(stderr, "Usage: ./tail -n\n");
return -1;
}else{
n = atoi(*argv + 1);
}
}
if (n > MAXLINES){
n = MAXLINES;
}else if (n <= 0){
return 0;
}
// allocate and initilize memory for n lines
char *lines_ptrs[n];
size_t lines_n[n];
for (i = 0; i < n; ++i){
lines_ptrs[i] = NULL;
lines_n[i] = 0;
}
// get lines
i = 0;
while (getline(&lines_ptrs[i], &lines_n[i], stdin) > 0){
if (++i >= n){ // loop around
i = 0;
}
}
// print lines in order
int j = i;
do{
if (lines_ptrs[j] != NULL){
printf("%s", lines_ptrs[j]);
free(lines_ptrs[j]);
}
if (++j >= n){ // loop around
j = 0;
}
}while(j != i);
}
Compilation and run:
$ gcc -o tail main.c
$ ./tail -3
0
1
2
3
4
2
3
4
Notes:
We make use of
geline
from standard library, check Exercise 1-17 for idiomatic way to call the function. In this case we make use ofgetline
to fill an array of lines. To fully understand this function we still need to know dynamic memory allocation which is explained at the end of Chapter 6.Notice that we use variable-length arrays, since the length of both
lines_ptrs
andlines_n
arrays is unknown at compile time (only at run time). This feature is from C99 onwards.
Rest of exercises¶
The documentation is not complete, but the solution code of the rest of the exercises can be consulted here:
https://github.com/Mr-Io/c-language-solutions/tree/master/solutions/chapter_5