Chapter 3¶
Exercise 3-1¶
main.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ARRAYLEN 1000000
#define SAMPLESIZE 10000
int binsearch(int x, const int v[], int n);
int binsearch1test(int x, const int v[], int n);
int main()
{
clock_t tic, toc;
int i;
int v[ARRAYLEN]; /* array to search */
for (i=0 ; i<ARRAYLEN; ++i){
v[i] = i;
}
int t[SAMPLESIZE]; /* random numbers to look for */
for (i=0 ; i<SAMPLESIZE; ++i){
v[i] = rand() % (ARRAYLEN + 1);
}
tic = clock();
for (i = 0 ; i < SAMPLESIZE ; ++i){
binsearch(t[i], v, ARRAYLEN);
}
toc = clock();
printf("binsearch: %6ld clock ticks \n", toc - tic);
tic = clock();
for (i = 0 ; i < SAMPLESIZE ; ++i){
binsearch1test(t[i], v, ARRAYLEN);
}
toc = clock();
printf("binsearch1test: %6ld clock ticks \n", toc - tic);
return 0;
}
int binsearch(int x, const int v[], int n)
{
int low, high, mid;
low = 0;
high = n - 1;
while (low <= high) {
mid = (low + high) / 2;
if (x < v[mid]){
high = mid - 1;
}else if (x > v[mid]){
low = mid + 1;
}else{
return mid;
}
}
return -1;
}
int binsearch1test(int x, const int v[], int n)
{
int low, high, mid=0;
low = 0;
high = n - 1;
while (high - low > 1) {
mid = (low + high) / 2;
if (x < v[mid]){
high = mid - 1;
}else{
low = mid;
}
}
if (v[low] == x){
return low;
}else if (v[mid] == x){
return mid;
}else if (v[high] == x){
return high;
}
return -1;
}
Compilation and run:
$ gcc main.c
$ ./a.out
binsearch: 1062 clock ticks
binsearch1test: 777 clock ticks
Notes:
We use
clock
function from<stdlib>
to give an approximate measure of CPU time (in clock ticks) used forbinsearch
andbinsearch1test
functions.
binsearch1test
is ~1.4x faster thanbinsearch
. This suggest that the compiler cannot predict very accurately that the last branch of if-else statement inbinsearch
is only hit once per call at most. So it may cause some missprediction penalty, increasing the CPU time. This can be researched further; Computer Systems - A Programmer’s Perspective is a good introductory book in the subject.
Note
Profiling is used to learn where a program spent its time, to identify the parts we should focus on in our optimization efforts. It is based on stack sampling technique.
Benchmarking is used to measure two or more competing options, in order to compare them. Different techniques may be used.
Using a profiler like gprof to measure the differences in run-time of the 2 functions would have been the wrong approach since a profiler should not be used to measure performance.
Exercise 3-2¶
main.c
#include <stdio.h>
#include <stdlib.h>
#define MAXLINE 80
void escape(char s[], const char t[]);
void unescape(char s[], const char t[]);
int main()
{
char s[MAXLINE];
char *t;
int len;
long unsigned n;
t = NULL;
n = 0;
while ((len = getline(&t, &n, stdin)) > 0){
if (len >= MAXLINE){
printf("error: line too long (max. size:%d)\n", MAXLINE);
return 1;
}
escape(s, t);
printf("%s\n", s);
unescape(t, s);
printf("%s", t);
}
free(t);
return 0;
}
void escape(char s[], const char t[])
{
int i, j;
for (i = 0, j = 0; t[i] != '\0'; ++i, ++j) {
switch (t[i]) {
case '\a':
s[j] = '\\';
s[++j] = 'a';
break;
case '\b':
s[j] = '\\';
s[++j] = 'b';
break;
case '\f':
s[j] = '\\';
s[++j] = 'f';
break;
case '\n':
s[j] = '\\';
s[++j] = 'n';
break;
case '\r':
s[j] = '\\';
s[++j] = 'r';
break;
case '\t':
s[j]= '\\';
s[++j] = 't';
break;
case '\\':
s[j] = '\\';
s[++j] = '\\';
break;
case '\?':
s[j] = '\\';
s[++j] = '\?';
break;
case '\'':
s[j] = '\\';
s[++j] = '\'';
break;
case '\"':
s[j] = '\\';
s[++j] = '\"';
break;
default:
s[j] = t[i];
}
}
s[j] = '\0';
}
void unescape(char s[], const char t[])
{
int i, j;
for (i = 0, j = 0; t[i] != '\0'; ++i, ++j) {
if (t[i] != '\\'){
s[j] = t[i];
}else{
switch (t[i+1]) {
case 'a':
s[j] = '\a';
++i;
break;
case 'b':
s[j] = '\b';
++i;
break;
case 'f':
s[j] = '\f';
++i;
break;
case 'n':
s[j] = '\n';
++i;
break;
case 'r':
s[j] = '\r';
++i;
break;
case 't':
s[j] = '\t';
++i;
break;
case '\\':
s[j] = '\\';
++i;
break;
case '\?':
s[j] = '\?';
++i;
break;
case '\'':
s[j] = '\'';
++i;
break;
case '\"':
s[j] = '\"';
++i;
break;
default:
s[j] = '\\';
}
}
}
s[j] = '\0';
}
Compilation and run:
$ gcc main.c
$ ./a.out
\Difficult subjects cannot be described with light prose
\\Difficult\tsubjects cannot be described\twith light prose\n
\Difficult subjects cannot be described with light prose
Notes:
Notice that, while we do not need to define the size of
t
because it is allocated bygetline
function, we do need to define the size of arrays
(dynamic memory allocation is not used until solutions of Chapter 5)
Exercise 3-3¶
main.c
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#define MAXLINE 100
int expand(const char s1[], char s2[], int n);
int main()
{
char *in;
char out[MAXLINE];
long unsigned n;
in = NULL;
n = 0;
while (getline(&in, &n, stdin) > 0){
if (expand(in, out, MAXLINE) == -1){
printf("error expand: insufficient length for s2 (max. size: %d)\n", MAXLINE);
return -1;
}
printf("%s", out);
};
free(in);
return 0;
}
int expand(const char s1[], char s2[], int n)
{
int i, j;
int start, mid, end;
for (i = 0, j = 0; s1[i] != '\0'; i++) {
start = s1[i];
mid = s1[i + 1];
end = mid != '\0' ? s1[i + 2] : '\0';
if (mid == '-' && end > start && ((isdigit(start) && isdigit(end)) ||
(isupper(start) && isupper(end)) ||
(islower(start) && islower(end)) )) {
while (end != start){
s2[j++] = start++;
if (j >= n){
return -1;
}
}
++i;
}else{
s2[j++] = s1[i];
if (j >= n){
return -1;
}
}
}
s2[j] = '\0';
return j;
}
Compilation and run:
$ gcc main.c
$ ./a.out
Normal usage: a-z k-p 0-9 1-4
Normal usage: abcdefghijklmnopqrstuvwxyz klmnop 0123456789 1234
Some things that should not convert: d-c a-4 3-1 b-9 a-Z a--z -z
Some things that should not convert: d-c a-4 3-1 b-9 a-Z a--z -z
Interesting case: a-z0-9
Interesting case: abcdefghijklmnopqrstuvwxyz0123456789
Interesting cases: -a-d- -4-5- a-b-c-d
Interesting cases: -abcd- -45- abcd
Notes:
We added an additional parameter to the original
expand
declaration to specifys2
size and avoid buffer overflowing, since it is not possible to know a priori how much the strings1
will be expanded.
Exercise 3-4¶
main.c
#include <stdio.h>
#include <limits.h>
#define CINTLEN 20
#define V_LEN 6
enum {POSITIVE, NEGATIVE};
void itoa(int n, char s[]);
void reverse(char s[]);
int main()
{
char s[CINTLEN];
int n;
while (scanf("%d", &n) == 1){
itoa(n, s);
printf("%s\n", s);
}
return 0;
}
void itoa(int n, char s[])
{
int i;
int sign;
unsigned un;
un = (unsigned) n;
sign = n < 0 ? NEGATIVE : POSITIVE;
if (n < 0){
un = ~un + 1;
}
i = 0;
do{
s[i++] = un % 10 + '0';
} while ((un /= 10) > 0);
if (sign == NEGATIVE){
s[i++] = '-';
}
s[i] = '\0';
reverse(s);
}
void reverse(char s[])
{
int i, j;
char c;
for (j = 0; s[j] != '\0'; ++j);
--j;
for (i = 0; i < j; ++i, --j) {
c = s[i];
s[i] = s[j];
s[j] = c;
}
}
Compilation and run:
$ gcc main.c
$ ./a.out
1234
1234
-4321
-4321
-2147483648
-2147483648
Notes:
The largest negative number cannot be handled in the book version of
itoa
becausen = -n;
fail; there is no positive representation of it (abs(INT_MAX) < abs(INT_MIN)
). We convert any negative value into a positive one by bit manipulation instead of using the unary-
operator. We useunsigned
type to manipulate bits, knowing that every absolute value of an integer in a two’s complement representation can be represented in an unsigned representation.Notice that the maximum lenght of
s
is not an aleatory number; we use the maximum string length of an integer of at least 8 bytes in a 2 complement representation (2**63 has 20 digits including-
). More precise would have been to take into account the byte length of anint
type usingsizeof
and to calculate the maximum number of digits.We reuse
reverse
function from Exercise 1-19.
Useful References:
Exercise 3-5¶
main.c
#include <stdio.h>
#include <limits.h>
#define CINTLEN 64
enum {POSITIVE, NEGATIVE};
int itob(int n, char s[], int b);
void reverse(char s[]);
int main()
{
char s[CINTLEN];
int n, b;
while (scanf("%d %d", &n, &b) == 2){
if (itob(n, s, b) != 0){
printf("error itob: invalid base (%d)\n", b);
return 1;
}
printf("%s\n", s);
}
return 0;
}
/* return 0 on sucess an -1 on error */
int itob(int n, char s[], int b)
{
int i;
int sign;
unsigned un;
if (b < 2 || b > (26+10)){
return -1;
}
un = (unsigned) n;
sign = n < 0 ? NEGATIVE : POSITIVE;
if (n < 0){
un = ~un + 1;
}
unsigned next;
i = 0;
do {
next = un % b;
s[i++] = (next <= 9) ? next + '0' : next - 10 + 'A';
} while ((un /= b) != 0);
if (sign == NEGATIVE){
s[i++] = '-';
}
s[i] = '\0';
reverse(s);
return 0;
}
void reverse(char s[])
{
int i, j;
char c;
for (j = 0; s[j] != '\0'; ++j);
--j;
for (i = 0; i < j; ++i, --j) {
c = s[i];
s[i] = s[j];
s[j] = c;
}
}
Compilation and run:
$ gcc main.c
$ ./a.out
15 16
F
15 2
1111
2147483647 2
1111111111111111111111111111111
28 28
10
27 28
R
111 -3
error itob: invalid base (-3)
Notes:
We use the same estructure as Exercise 3-4. One important difference between
itoa
anditob
is thatitob
return anint
to report errors to the caller.
Useful References:
Exercise 3-6¶
main.c
#include <stdio.h>
#include <limits.h>
#include <string.h>
#define CINTLEN 100
#define V_LEN 6
enum {POSITIVE, NEGATIVE};
int itoa_v2(int n, char s[], int blanks);
void itoa(int n, char s[]);
void reverse(char s[]);
int main()
{
char s[CINTLEN];
int n;
int width;
while (scanf("%d %d", &n, &width) == 2){
if(itoa_v2(n, s, width) != 0){
printf("error itoa: %d too big (max. width: %d)\n", width, CINTLEN);
return 1;
}
printf("%s\n", s);
}
return 0;
}
/* return 0 if success and -1 on error */
int itoa_v2(int n, char s[], int blanks)
{
itoa(n, s);
int len = strlen(s);
if (blanks > CINTLEN){
return -1;
}else if (blanks > len){
int i, j;
for (i = blanks, j = len; i >= 0 ; --i, --j){
s[i] = j >= 0 ? s[j] : ' ';
}
}
return 0;
}
void itoa(int n, char s[])
{
int i;
int sign;
unsigned un;
un = (unsigned) n;
sign = n < 0 ? NEGATIVE : POSITIVE;
if (n < 0){
un = ~un + 1;
}
i = 0;
do{
s[i++] = un % 10 + '0';
} while ((un /= 10) > 0);
if (sign == NEGATIVE){
s[i++] = '-';
}
s[i] = '\0';
reverse(s);
}
void reverse(char s[])
{
int i, j;
char c;
for (j = 0; s[j] != '\0'; ++j);
--j;
for (i = 0; i < j; ++i, --j) {
c = s[i];
s[i] = s[j];
s[j] = c;
}
}
Compilation and run:
$ gcc main.c
$ ./a.out
t
1234 1
1234
1234 5
1234
-123 5
-123
-2147483648 15
-2147483648
12345 999
error itoa: 999 too big (max. width: 100)
Notes:
We use the code of Exercise 3-4 and instead of modifying
itoa
we make a wrapper functionitoa_v2
.
Useful References: