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
getcandungetcfrom<stdio.h>instead ofgetchandungetchdefined 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 (stdinin this case). The solutions of exercises of Chapter 7 are all about input and output.
ungetcmay fail to push somecvalues. Specifically, if the value ofcequals that of the macroEOF, the operation fails and the input stream is unchanged. This behaviour is alright, so we do not protectungetcfrom receivingEOFand we also do not check forungetcerrors.
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
strcattomystrcatbecause there is a previous declaration ofstrcatin<stdio>.It is responsibility of the caller to check that
shas enough space to concatenate the stringtat 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
strcattomystrcatbecause there is a previous declaration ofstrcatin<stdio>.It is responsibility of the caller to check that
shas enough space to concatenate the stringtat 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:
mygetlineis a rewrite the one from Exercise 1-16 but it does not store the ending\nchar in the buffer.atoiis a rewrite of the one defined in the standard library.itoaandreverseare rewrites of the ones from Exercise 3-4.strindexis a rewrite of the one defined in K&R page 69.getopis 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:
mygetlineis the same as the one in Exercise 5-6.We use
getlinefrom standard library to fill an array of pointer. We usemygetlineto fill an array of arrays. The former case is ~1.6 faster, this implies thatgetlinefrom standard library is more efficient thanmygetline, even when it allocates memory dynamically.Notice the difference in the first argument of
readline_staticandreadline_dynamic:
char **lineptris 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
pushandpop).
isnumberis the same as the one defined in Exercises 4-3, 4-4, 4-5, 4-6, but we make use of theconstqualifier 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
argvis 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
gelinefrom standard library, check Exercise 1-17 for idiomatic way to call the function. In this case we make use ofgetlineto 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_ptrsandlines_narrays 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