Unix tail command

My assignment is to mimic the Unix tail command. For those of you who do not know it, it prints out the last n lines of a file. I think I am going to have a lot of questions.

We can't use std::vector<std::strings>, we must use char**

So the char** is allocated to hold n lines, and then each char* in the array is allocated to hold the line. We make this buffer circular, and end up with the three variables:
1
2
3
char** lines;
char** current;
char** end;


current is used to iterate through the lines, and end is to know when the end of the array is found so that current will go back to the start (lines).

I've got a problem right off the bat...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int createLines(unsigned int amount)
{
  /* General Safety */
  if (lines != NULL) return -1;
    amount = (amount < MAX_LINES? amount: MAX_LINES);
  
  /* Create Lines */
  lines = new char*[amount];

  /* Define important positions*/
  current = lines;
  end = lines + (sizeof(char*) * (amount - 1));

  std::cout << lines << " " << current << " " << end << std::endl;

  /* Set array to NULL*/
  int i;
  for (i = 0; i < amount; i++)
  {
    std::cout << &lines[i] << std::endl;
    lines[i] = NULL;
  }
  return 0;
}


When this function is given an amount of 2, I get the output
00345B88 00345B88 00345B98
00345B88
00345B8C


I think the value of 'end' isn't correct. Shouldn't it be 00345B8C?

Edit: changing line 12 to be sizeof(char) gives me the right number? I must be thinking about memory wrong.
Last edited on
Pointer arithmetic already accounts for different object sizes, it's not your job to do that.
The only reason sizeof(char) "works" is because it is 1 by definition and thus the multiplication does not affect the result.
Ah, thanks. 16 (0x10) would be expected.

Now for the printing part. 'end' is the last line of the array and it is a valid address. Is this safe?
1
2
3
4
5
6
7
8
9
10
11
void print(void)
{
  char** iter;
  if (current == NULL || current == lines)
  {
    iter = lines;
    while (*iter != NULL || iter == end + 1)
      std::cout << *iter++ << std:endl;
  }
  // The tricky part goes here
}


In particular: iter == end + 1

Edit: other than the bug you may see when the initial amount is greater than the amount of lines actually read :)
Last edited on
Having a pointer to the element just one past the array is allowed. However, dereferencing it is not and incrementing it further is not allowed either. It seems you meant if (iter != end + 1 && *iter != NULL)
Last edited on
Looks like we're up and running :).

For those of you looking for the bug I mentioned current in line 4 needs to be *current.

Seeing as how there have been a lot of "pointer" questions these days, here is the code:
lineholder.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#include "lineholder.h" // Function definitions and #include <iostream>

#define NULL 0
#define MAX_LINES 1000
char** lines = NULL;
char** current = NULL;
char** end = NULL;

int createLines(unsigned int amount)
{
	/* General Safety */
	if (lines != NULL || amount == 0) return -1;
	amount = (amount < MAX_LINES? amount: MAX_LINES);

	/* Create Lines */
	lines = new char*[amount];

	/* Define important positions*/
	current = lines;
	end = lines + (amount - 1);

	/* Set array to NULL*/
	char** iter = lines;
	while (iter <= end)
	{
		*iter++ = NULL;
	}

	return 0;
}
void destroyLines(void)
{
	/* General Safety */
	if (lines == NULL) return;

	/* Create an Iterator */
	char** iter = lines;

	/* Delete each line */
	while (iter <= end)
	{
		if (*iter != NULL)
			delete [] *iter;
		iter++;
	}
	/* Delete entire Array */
	delete [] lines;

	/* Reset Variables */
	lines = NULL;
	current = NULL;
	end = NULL;
}
int addLine(const char* line)
{
	
	/* Delete an existing line  */
	if (*current != NULL) delete [] *current;
	
	
	/* Allocate Space and Copy line */
	*current = new char[myStrlen(line) + 1];
	myStrcpy(*current, line);


	/* Move Current */
	if (current == end)
	{
		current = lines;
	}
	else
	{
		current++;
	}
	return 0;
}

void print(void)
{
	/* Create an Iterator */
	char** iter;

	/* If the file was smaller than the allocated array
	 * Or the array size was divisible by the lines in the file */
	if (*current == NULL || current == lines)
	{
		iter = lines;
		while (iter != end + 1 && *iter != NULL)
		{
			std::cout << *iter++ << std::endl;
		}
	}
	else /* We have read over the array more than once
		  * And current is somewhere in the middle of it */
	{
		iter = current;
		do
		{
		std::cout << *iter << std::endl;
		if (iter == end) iter = lines;
		else iter++;
		}
		while (iter != current);
	}
}

unsigned int myStrlen(const char* str)
{
	const char* pos = str;
	while (*str++)
		;
	return str - pos - 1;
}
void myStrcpy(char* dest, const char* source)
{
	while (*dest++ = *source++)
		;
}


And a main to go with it:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "lineholder.h"
#include <iostream>


int main(void)
{
	createLines(2);

	addLine("Hello");
	addLine("World");
	addLine("Goodbye");

	print();

	destroyLines();
	return 0;
}
Last edited on
Topic archived. No new replies allowed.