Chapter 4¶
Exercise 4-1¶
main.c
#include <stdio.h>
#include <string.h>
int strrindex(const char s[], const char t[]);
int main()
{
char *s;
char *t;
long unsigned ns, nt;
s = t = NULL;
ns = nt = 0;
while (getline(&s, &ns, stdin) > 0){
if (getline(&t, &nt, stdin) <= 0){
printf("getline: error reading 2nd line\n");
return 1;
}
printf("position of last occurence: %d\n", strrindex(s, t));
}
return 0;
}
int strrindex(const char s[], const char t[])
{
int i_start; /* initial position to start comparing*/
int i, j, k;
i_start = strlen(s) - strlen(t);
for (i = i_start; i >= 0; --i) {
for (k = i, j = 0; s[k] == t[j] && t[j] != '\n' && t[j] != '\0' ; ++k, ++j);
if (t[j] == '\n' || t[j] == '\0'){
return i;
}
}
return -1;
}
Compilation and run:
$ gcc main.c
$ ./a.out
Knowledge is power
ow
position of last occurence: 14
Exercise 4-2¶
main.c
#include <stdio.h>
#include <ctype.h>
#include <math.h>
double atof(const char s[]);
int main()
{
char *s;
long unsigned n;
s = NULL;
n = 0;
while (getline(&s, &n, stdin) > 0){
printf("%f\n", atof(s));
}
return 0;
}
double atof(const char s[])
{
/* ignore trailing whitespaces and get sign */
int i, sign;
for (i = 0; s[i] == ' '; ++i);
sign = s[i] == '-' ? -1 : 1;
if (s[i] == '-' || s[i] == '+'){
++i;
}
/* get absolute integer value */
double val = 0;
while (isdigit(s[i])){
val = 10 * val + (s[i] - '0');
++i;
}
/* add decimals */
if (s[i] == '.') {
int dec = 1;
++i;
while (isdigit(s[i])) {
val = 10 * val + (s[i] - '0');
dec *= 10;
++i;
}
val /= dec;
}
/* add exponents */
if (s[i] == 'e' || s[i] == 'E') {
int expsign, exp=0;
++i;
expsign = (s[i] == '-') ? -1 : 1;
if (s[i] == '-' || s[i] == '+')
++i;
while (isdigit(s[i])) {
exp = 10 * exp + (s[i] - '0');
++i;
}
val = val * pow(10.0, expsign * exp);
}
return sign * val;
}
Compilation and run:
$ gcc main.c
$ ./a.out
234
234.000000
0.123
0.123000
1234.
1234.000000
123.123
123.123000
123.2e4
1232000.000000
123.123e-3
0.123123
-1234
-1234.000000
-1234.0
-1234.000000
-1234.
-1234.000000
-1234.2e4
-12342000.000000
-1234.234e4
-12342340.000000
-1234.123e-4
-0.123412
Notes:
The option
-lmtell GCC to link the math library. This is necesary because the math library is not included by default.
Note
In general, it is usual to start library file names
with “lib”. This way, -lname search
for a library file named libname.so in a specific
list of directories. We can add new directories to the list
using the option -L/another/path/to/search.
If we add the option -static it searchs and links
static libraries instead (libname.a).
The standard library libc.so is always included by default.
Exercises 4-3, 4-4, 4-5, 4-6¶
main.c
#include "tokenizer.h"
#include "stack.h"
#include "variables.h"
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define MAXOP 32
/* reverse Polish calculator */
int main()
{
int type;
char s[MAXOP];
double op1, op2;
int iop2;
char var;
double vprinted;
int mssg;
mssg = 0;
initvar();
while ((type = gettoken(s, MAXOP)) != EOF) {
switch (type) {
case NUM:
op1 = atof(s);
if (push(op1) == -1){
printf("error: full stack, can't push %f \n", op1);
mssg = 1;
}
break;
case VAR:
var = s[0];
if (hasvalue(var)){
if (push(getvar(var)) == -1){
printf("error: full stack, can't push %f \n", op1);
mssg = 1;
}
}else{
setvar(var, pop());
}
break;
case '_':
op1 = vprinted;
if (push(op1) == -1){
printf("error: full stack, can't push %f \n", op1);
mssg = 1;
}
break;
case '\n':
if (!mssg){
printvar();
printstack();
}else{
mssg = 0;
}
printf("> ");
vprinted = peek();
break;
case '+':
push(pop() + pop());
break;
case '*':
push(pop() * pop());
break;
case '-':
op2 = pop();
push(pop() - op2);
break;
case '/':
op2 = pop();
if (op2 != 0.0){
push(pop() / op2);
}else{
printf("error: division by 0\n");
mssg = 1;
}
break;
case '%':
iop2 = (int) pop();
if (iop2 != 0){
push((int)pop() % iop2);
}else{
printf("error: division by 0\n");
mssg = 1;
}
break;
case POW:
op2 = pop();
push(pow(pop(), op2));
break;
case EXP:
push(exp(pop()));
break;
case SIN:
push(sin(pop()));
break;
case SWP:
swap();
break;
case DUP:
dup();
break;
case DEL:
pop();
break;
case CLR:
clearstack();
break;
case ERR:
printf("error: input ignored\n");
mssg = 1;
break;
default:
printf("error: Invalid input '%s'\n", s);
mssg = 1;
break;
}
}
/* we should free memory allocated with getline in myio.c here */
}
getoptwas vulnerable to buffer overflows, wo we added thenparameter and change its name togettoken. Same asgetopt,gettokensaves the value of a token insand returns its type. The type of token is used in the switch case statement to decide which operation to do next.The modulus operator
%(indicated in exercise 4-3) is added in the switch case statement the same way as the rest of operators.The commands of exercise 4-4 and the math functionalities of exercise 4-5 are added using the different types returned by
gettoken:SIN,POW,EXP,DUP… wich are defined in tokenizer.h. This could have been done in another way if we could use pointers to functions, making the switch case much shorter. Exercises with pointers are solved in Chapter 5.The stack is always visible, we do not need to pop an element to see its value and
\nno longer removes an item from the stack, it needs to be done explicitly with del command.To add the variable functionality from exercise 4-6 it would have been best to allow the stack to hold variables
a-zaside from doubles values. However without using unions or structs from Chapter 6 the implementation would have been messy. What we did instead is make variables work in a simple way: when we enter a variable for the first time it saves the previous number, the next time we use the same variable it evaluates to the number saved. The list of saved variables is also visible while using the calculator.
tokenizer.h
#define ERR '0'
#define VAR '1'
#define NUM '2'
#define POW '3'
#define EXP '4'
#define SIN '5'
#define SWP '6'
#define DUP '7'
#define DEL '8'
#define CLR '9'
int gettoken(char s[], int n);
void printstack(void);
tokenizer.c
#include "tokenizer.h"
#include "myio.h"
#include <stdio.h>
#include <ctype.h>
int isnumber(char s[]);
int ismath(char s[], char op[]);
int isvar(char s[]);
/*
* store a word in buffer s, indicating n as its maximum size
* trailing blanks are removed.
* a newline is considered a word of length 1, independently if it is colliding or now with another word.
* return the number of character readed
* return 0 if EOF before any word or newline is encountered
* return -1 if buffer s is not long enough to hold the word
*/
int getword(char s[], unsigned n);
int gettoken(char s[], int n)
{
/* get next word */
if (getword(s, n) == -1){
return -1;
}
/* check its type: digit, math, variable or operator */
if (isnumber(s)){
return NUM;
}else if (isvar(s)){
return VAR;
}else if (ismath(s, "pow")){
return POW;
}else if (ismath(s, "exp")){
return EXP;
}else if (ismath(s, "sin")){
return SIN;
}else if (ismath(s, "swap")){
return SWP;
}else if (ismath(s, "dup")){
return DUP;
}else if (ismath(s, "del")){
return DEL;
}else if (ismath(s, "clear")){
return CLR;
}else if (isvar(s)){
return VAR;
}
return s[0];
}
int getword(char s[], unsigned n)
{
int c, i;
/* remove trailing blank lines */
i = 0;
while(isblank(c = getch()));
if (c != EOF){
s[i++] = c;
}
/* return if '\n' */
if (c == '\n'){
s[i] = '\0';
return 1;
}
/* read every character until a blank or newline */
while(i < n && (c = getch()) != EOF && !isblank(c) && c != '\n'){
s[i++] = c;
}
/* error if n is not long enough - we do not try to recover */
if (i >= n){
return -1;
}
/* ungetch last character and return */
s[i] = '\0';
if (ungetch(c) == -1){
return -1;
}
return i;
}
/* return 1 if s represent a number or 0 if not */
int isnumber(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;
}
int ismath(char s[], char op[])
{
int i;
for (i = 0 ; s[i] == op[i] && s[i] != '\0' ; ++i);
if (s[i] == op[i] && s[i] == '\0'){
return 1;
}
return 0;
}
int isvar(char s[])
{
if (s[0] >= 'a' && s[0] <= 'z' && s[1] == '\0'){
return 1;
}
return 0;
}
getoptfunctionality is refactored in different functions. As we have saidgettokenis the interface; a safe function that saves a token insand returns its value.getwordis used to retrieve a word from the stdin (a modified version of Exercise 1-16) andisnumber,ismathandisvarare used to determine its type. All the possible types are defined in tokenizer.h for other files to use.Notice that in the header, we only includes the functions that are intended to be used externally by other files. It does not include
getword,isnumber,ismathandisvarsince those are intended for internal use only.
stack.h
/* return the number on success and NAN on error */
double pop(void);
double peek(void);
/* return 0 on success and -1 on error */
int push(double x);
int swap(void);
int dup(void);
void clearstack(void);
void printstack(void);
stack.c
#include "stack.h"
#include <stdio.h>
#include <math.h>
#define MAXSTACK 16
double stack[MAXSTACK];
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;
}
void clearstack(void)
{
rsp = 0;
}
double peek(void)
{
if (rsp == 0){
return NAN;
}
return stack[rsp];
}
int swap(void)
{
if (rsp < 2){
return -1;
}
double tmp;
tmp = stack[rsp-2];
stack[rsp-2] = stack[rsp-1];
stack[rsp-1] = tmp;
return 0;
}
int dup(void)
{
if (rsp < 1 || rsp + 1 >= MAXSTACK ){
return -1;
}
double tmp = stack[rsp-1];
stack[rsp++] = tmp;
return 0;
}
void printstack(void)
{
int i;
for(i = 0 ; i < rsp ; ++i){
printf("%d: %f\n", rsp-i-1, stack[i]);
}
}
Additional functions has been added to the stack module to implement the additional functionality that the exercises required. An argument could be made about implementing duplicate and swap logic in main instead of here.
myio.h
int getch(void);
/* return 0 on success and -1 on error */
int ungetch(int c);
/* push s string into the input, return 0 on success and -1 on error */
int ungets(char s[]);
myio.c
#include "myio.h"
#include <stdio.h>
#include <ctype.h>
#define BUFSIZE 16
char buff[BUFSIZE];
int ibuf = 0; /* next free position in buff */
int getch(void)
{
return (ibuf > 0) ? buff[--ibuf] : getchar();
}
int ungetch(int c)
{
if (ibuf >= BUFSIZE || c == EOF) {
return -1;
}
buff[ibuf++] = c;
return 0;
}
/* push s string into the input, return 0 on success and -1 on error */
int ungets(char s[])
{
/* check if there is space*/
int i, len;
for (len = 0; s[len] != 0 ; ++i);
if (len > BUFSIZE - ibuf){
return -1;
}
/* push s in the correct order , ungetch should not fail */
for (i = len -1 ; i >= 0 ; --i){
if (ungetch(s[i]) == -1){
return -1;
}
}
return 0;
}
ungetsroutine is implemented in myio module. It just usesungetchto implement its functionality, it does not touchbuffdirectly.
variables.h
/* always call before using the module*/
void initvar(void);
/* get the value of variable var or NAN if it has not been used yet */
double getvar(char var);
/* set the value of variable var to x */
void setvar(char var, double x);
/* return 1 if the variable var has value or 0 if it has not been used yet */
int hasvalue(char var);
void printvar(void);
variables.c
#include "variables.h"
#include <stdio.h>
#include <math.h>
#define VARSIZE ('z' - 'a' + 1)
double vars[VARSIZE];
/* initialize variables values */
void initvar(void)
{
int i;
for (i = 0 ; i < VARSIZE ; ++i){
vars[i] = NAN;
}
}
double getvar(char var)
{
return vars[var - 'a'];
}
void setvar(char var, double x)
{
vars[var - 'a'] = x;
}
int hasvalue(char var)
{
return !isnan(vars[var - 'a']);
}
void printvar(void)
{
double v;
int somevar;
int i;
somevar=0;
printf("(");
for (i = 0 ; i < VARSIZE ; ++i){
v = vars[i];
if (!isnan(v)){
somevar = 1;
printf("%c=%.1f, ", 'a' + i, v);
}
}
if (somevar){
printf("\b\b)\n");
}else{
printf("\b");
}
}
We make another module (variables.h and variables.c) to implement variable evaluation logic. It is used directly by main.c.
Compilation and run:
$ gcc main.c variables.c tokenizer.c myio.c stack.c -lm
$ ./a.out
12 34 -22
2: 12.000000
1: 34.000000
0: -22.000000
> 2 2 +
3: 12.000000
2: 34.000000
1: -22.000000
0: 4.000000
> 3 -
3: 12.000000
2: 34.000000
1: -22.000000
0: 1.000000
> +
2: 12.000000
1: 34.000000
0: -21.000000
> dup
3: 12.000000
2: 34.000000
1: -21.000000
0: -21.000000
> exp
3: 12.000000
2: 34.000000
1: -21.000000
0: 0.000000
> del
2: 12.000000
1: 34.000000
0: -21.000000
> a
(a=-21.0)
1: 12.000000
0: 34.000000
> b
(a=-21.0, b=34.0)
0: 12.000000
> a b +
(a=-21.0, b=34.0)
1: 12.000000
0: 13.000000
> b pow
(a=-21.0, b=34.0)
1: 12.000000
0: 74829695578286079398588608906795155456.000000
> swp
error: Invalid input 'swp'
> swap
(a=-21.0, b=34.0)
1: 74829695578286079398588608906795155456.000000
0: 12.000000
> clear
(a=-21.0, b=34.0)
>
Exercise 4-8¶
Same as Exercises 4-3, 4-4, 4-5, 4-6 but with the following modifications
to myio.h and myio.c:
myio.h
int getch(void);
/* return 0 on success and -1 on error */
int ungetch(int c);
myio.c
#include "myio.h"
#include <stdio.h>
#include <ctype.h>
#define BUFSIZE 16
char buffchar;
int ibuf = 0; /* 0 not saved and 1 saved */
int getch(void)
{
if(ibuf){
ibuf = 0;
return buffchar;
}
return getchar();
}
int ungetch(int c)
{
if (ibuf || c == EOF) {
return -1;
}
buffchar = c;
ibuf = 1;
return 0;
}
Notes:
There is no longer a buffer array, just one character buffer.
ibufno longer represent the position in the buffer, it only indicates if there is a character saved in the buffer or not.
getchandungetchare modified to work with the new versions ofbuffcharandibuf.
ungetsget removed since it depended on having a buffer array (it was not used anyways).
Exercise 4-9¶
Same as Exercise 4-8 but modifying myio.c:
myio.c
#include "myio.h"
#include <stdio.h>
#include <ctype.h>
#define BUFSIZE 16
int buffchar;
int ibuf = 0; /* 0 not saved and 1 saved */
int getch(void)
{
if(ibuf){
ibuf = 0;
return buffchar;
}
return getchar();
}
int ungetch(int c)
{
if (ibuf) {
return -1;
}
buffchar = c;
ibuf = 1;
return 0;
}
Notes:
We just changed the type of
buffcharfrominttochar, this way it can holdEOFvalues aside from characters.Notice that
ungetchno longer return error when trying to pushEOF.
Exercise 4-10¶
Same as Exercise 4-9 but modifying myio.c:
myio.c
#include "myio.h"
#include <stdio.h>
#include <ctype.h>
unsigned long nb = 0;
char *buff = NULL;
int len = 0; /* number of character readed by getline */
int pos = 0; /* current buffer position */
/* return 0 success and -1 on error */
int fillbuff(void)
{
if ((len = getline(&buff, &nb, stdin)) < 0){
return -1;
}
pos = 0;
return 0;
}
int getch(void)
{
/* fill buffer if it is empty */
if (len - pos == 0){
if(fillbuff() == -1){
return -1;
}
}
return buff[pos++];
}
/* push s string into the input, return 0 on success and -1 on error */
int ungetch(int c)
{
if (pos == 0) {
return -1;
}
buff[--pos] = c;
return 0;
}
Notes:
Until now, we were using
getcharfrom stdlib to read from the standard input. This exercise consist on organizing the code to usegetlineinstead ofgetcharfor reading from the standard input. The only functions that depends ongetchararegetchandungetchand, instead of getting rid of them and refactoring the whole program, we just make them usegetlineinstead.
Exercise 4-11¶
Same as Exercises 4-3, 4-4, 4-5, 4-6 but modifying getword
from tokenizer.c and removing myio.h and myio.c:
tokenizer.c
#include "tokenizer.h"
#include "myio.h"
#include <stdio.h>
#include <ctype.h>
int isnumber(char s[]);
int ismath(char s[], char op[]);
int isvar(char s[]);
/*
* store a word in buffer s, indicating n as its maximum size
* trailing blanks are removed.
* a newline is considered a word of length 1, independently if it is colliding or now with another word.
* return the number of character readed
* return 0 if EOF before any word or newline is encountered
* return -1 if buffer s is not long enough to hold the word
*/
int getword(char s[], unsigned n);
int gettoken(char s[], int n)
{
/* get next word */
if (getword(s, n) == -1){
return -1;
}
/* check its type: digit, math, variable or operator */
if (isnumber(s)){
return NUM;
}else if (isvar(s)){
return VAR;
}else if (ismath(s, "pow")){
return POW;
}else if (ismath(s, "exp")){
return EXP;
}else if (ismath(s, "sin")){
return SIN;
}else if (ismath(s, "swap")){
return SWP;
}else if (ismath(s, "dup")){
return DUP;
}else if (ismath(s, "del")){
return DEL;
}else if (ismath(s, "clear")){
return CLR;
}else if (isvar(s)){
return VAR;
}
return s[0];
}
int getword(char s[], unsigned n)
{
int c, i;
/* remove trailing blank lines */
i = 0;
while(isblank(c = getch()));
if (c != EOF){
s[i++] = c;
}
/* return if '\n' */
if (c == '\n'){
s[i] = '\0';
return 1;
}
/* read every character until a blank or newline */
while(i < n && (c = getch()) != EOF && !isblank(c) && c != '\n'){
s[i++] = c;
}
/* error if n is not long enough - we do not try to recover */
if (i >= n){
return -1;
}
/* ungetch last character and return */
s[i] = '\0';
if (ungetch(c) == -1){
return -1;
}
return i;
}
/* return 1 if s represent a number or 0 if not */
int isnumber(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;
}
int ismath(char s[], char op[])
{
int i;
for (i = 0 ; s[i] == op[i] && s[i] != '\0' ; ++i);
if (s[i] == op[i] && s[i] == '\0'){
return 1;
}
return 0;
}
int isvar(char s[])
{
if (s[0] >= 'a' && s[0] <= 'z' && s[1] == '\0'){
return 1;
}
return 0;
}
- Notes:
remember that *
getchandungetchwere just wrappers aroundgetchar(orgetline) from the standard library to implememt the “unget character” functionality (which the standard library already have btw). Since we do not need that functionality anymore, we can get rid of them and of the whole myio module (myio.h and myio.c).
Exercise 4-12¶
main.c
#include <stdio.h>
#define CINTLEN 20
int itoa(char s[], int n);
int main()
{
char s[CINTLEN];
int n;
while (scanf("%d", &n) == 1){
itoa(s, n);
printf("%s\n", s);
}
return 0;
}
int itoa_rec(char s[], unsigned n);
int itoa(char s[], int n)
{
/* handle negative numbers */
unsigned un;
un = (unsigned) n;
if (n < 0){
un = ~un + 1;
s[0] = '-';
++s;
}
/* recursive logic */
int total_digits;
total_digits = itoa_rec(s, un);
/* finish s with null character '\0' */
s[total_digits] = '\0';
}
/*
* convert a integert n into a string s
* (it only works for positive numbers and it does not add '\0' to end)
* it returns the number of digits of n
*/
int itoa_rec(char s[], unsigned n)
{
int n_digits;
/* case base */
if (n == 0){
return 0;
}
/* recursive case */
n_digits = itoa_rec(s, n/10) + 1;
s[n_digits-1] = n % 10 + '0';
if (n < 10){
s[n_digits] = '\0';
}
return n_digits;
}
Compilation and run:
$ gcc main.c
$ ./a.out
1234
1234
-1234
-1234
Notes:
Refer to Exercise 3-4 to understand why we use bitwise operators to handle negative numbers and to understand why we dont need to implement buffer overflow control even when
CINTLENis just 20.
Exercise 4-13¶
main.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void reverse(char s[]);
int main()
{
char *s;
int len;
long unsigned n;
s = NULL;
n = 0;
while((len = getline(&s, &n, stdin)) > 0){
reverse(s);
printf("%s\n", s);
}
free(s);
return 0;
}
void reverse_rec(char s[], int start, int end);
void reverse(char s[])
{
int len = strlen(s);
if (len > 0 && s[len - 1] == '\n'){
s[--len] = '\0';
}
reverse_rec(s, 0, strlen(s)-1);
}
void reverse_rec(char s[], int start, int end)
{
// base case
if (start >= end)
return;
// recursion
char tmp;
tmp = s[start];
s[start] = s[end];
s[end] = tmp;
reverse_rec(s, ++start, --end);
}
Compilation and run:
$ gcc main.c
$ ./a.out
Hello!
!olleH
Is this working fine??
??enif gnikrow siht sI
Notes:
We used getline from standard library to read lines from input, see notes on Exercise 1-17 to remember its usage.
Exercise 4-14¶
main.c
#include <stdio.h>
#define swap(t, x, y) { t temp = x; \
x = y; \
y = temp; \
}
int main()
{
int x;
int y;
while(scanf("%d %d", &x, &y)){
swap(int, x, y);
printf("%d %d\n", x, y);
}
return 0;
}
Compilation and run:
$ gcc main.c
$ ./a.out
1 2
2 1
123 -44
-44 123