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 and ungetc from <stdio.h> instead of getch and ungetch 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 some c values. Specifically, if the value of c equals that of the macro EOF, the operation fails and the input stream is unchanged. This behaviour is alright, so we do not protect ungetc from receiving EOF and we also do not check for ungetc 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:

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 to mystrcat because there is a previous declaration of strcat in <stdio>.

  • It is responsibility of the caller to check that s has enough space to concatenate the string t 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 to mystrcat because there is a previous declaration of strcat in <stdio>.

  • It is responsibility of the caller to check that s has enough space to concatenate the string t 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 and reverse 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 use mygetline to fill an array of arrays. The former case is ~1.6 faster, this implies that getline from standard library is more efficient than mygetline, even when it allocates memory dynamically.

  • Notice the difference in the first argument of readline_static and readline_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 because char *lines[LINESIZE] is equivalent to char *lines[], which is just the same as char **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 and pop).

  • isnumber is the same as the one defined in Exercises 4-3, 4-4, 4-5, 4-6, but we make use of the const 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 of getline 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 and lines_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