Kills log problem

Started by
6 comments, last by NotAYakk 14 years, 12 months ago
C++, I'm writing a function to add a line to a kills log, the point is like this: e.g. first it would look like this
line 5
line 4
line 3
line 2
line 1
then when the function is called it adds a new line and deletes the last one
line 6
line 5
line 4
line 3
line 2
The following code works correctly if I call it in the game init, but during game loop, e.g. when someone gets killed and there's this function to add a new line to the killsList, it crashes the game. Why? MAX_KILLSLOG is defined as 5
void CGame::AddKillToList(std::string text)
{
	int i;

	for(i=0;i<MAX_KILLSLOG;i++)
	{
		if(MAX_KILLSLOG-(i+1) <= MAX_KILLSLOG)
		{
			killsList[MAX_KILLSLOG-i] = killsList[MAX_KILLSLOG-(i+1)];
		}
	}

	killsList[0] = text;
}

What the h*ll are you?
Advertisement
What's killsList?
It's
std::string killsList[MAX_KILLSLOG];
What the h*ll are you?
And what kind of crash are you getting?
Just closes the app and nothing else
What the h*ll are you?
killsList[MAX_KILLSLOG-i] = killsList[MAX_KILLSLOG-(i+1)];

Wouldn't that crash if MAX_KILLSLOG is 5 and i = 0 then you are trying to index out of the bounds?
Let us think about the code, and look for boundary conditions.
void CGame::AddKillToList(std::string text){	int i;	for(i=0;i<MAX_KILLSLOG;i++)	{		if(MAX_KILLSLOG-(i+1) <= MAX_KILLSLOG)		{			killsList[MAX_KILLSLOG-i] = killsList[MAX_KILLSLOG-(i+1)];		}	}	killsList[0] = text;}

Consider if i is 0. The condition evaluates to MAX_KILLSLOG-1 <= MAX_KILLSLOG, which is true. Then the next line executes this:
killsList[MAX_KILLSLOG] = killsList[MAX_KILLSLOG +1];

This is (probably, because you haven't shown us enough code) going beyond the bounds of the array, which could cause a crash (it is undefined behaviour).

Instead of fixing the logic, there is an alternative trick. Instead of moving the elements in the array around, we can access them in an odd fashion with an offset.

Example:
class Renderer{public:	void renderText(const std::string &text) { std::cout << text << std::endl; }};class KillList{public:	KillList()	:		start(0)	{	}	void add(const std::string &kill);	void render(Renderer &renderer);private:	static const int MAX = 5;	std::string log[MAX];	int start;};void KillList::add(const std::string &kill){	log[start] = kill;	start = (start + 1) % MAX;}void KillList::render(Renderer &renderer){	for(int i = MAX - 1 ; i >= 0 ; --i)	{		int index = (start + i) % MAX;		renderer.renderText(/* position, etc ... */ log[index]); 	}}int main(){	Renderer renderer;	KillList list;	int i = 0;	std::string people[5] = {		"jack", "jim", "john", "dave", "donald sutherland"	};	while(++i < 20)	{		int i = rand() % 5;		int j = rand() % 5;		if(i != j)		{			list.add("\t" + people + " killed " + people[j]);		}		renderer.renderText("kills {");		list.render(renderer);		renderer.renderText("}");	}}

So, what happens is that the array has a marker as to where we should next insert a kill. Every time we insert a kill the marker is moved to the next element (or looped around to point at the first if we run out).

When we print, we just print in reverse order. So our loop goes from MAX - 1 to 0. But, we need to take into account the offset variable "next". Hence the additional "index" calculation.

This is basically a ring buffer.
template<typename T, size_t n>struct RingBuffer {  T data[n];  size_t used;  RingBuffer():top(0) {}  void push_back( T const& src ) {    ++used;    (*this)[used] = src;  }  T& operator[](size_t i) {    return data;<br>  }<br>  T <span class="cpp-keyword">const</span>&amp; <span class="cpp-keyword">operator</span>[](size_t i) <span class="cpp-keyword">const</span> {<br>    <span class="cpp-keyword">return</span> data;<br>  }<br><br>  <span class="cpp-keyword">struct</span> iterator {<br>    size_t index;<br>    RingBuffer* buff;<br><br>    T&amp; <span class="cpp-keyword">operator</span>*() { <span class="cpp-keyword">return</span> (*buff)[index]; }<br>    T* <span class="cpp-keyword">operator</span>-&gt;() { <span class="cpp-keyword">return</span> &amp;( (*buff)[index] ); }<br>    iterator&amp; <span class="cpp-keyword">operator</span>++() { ++index; <span class="cpp-keyword">return</span> *<span class="cpp-keyword">this</span>; }<br>    iterator <span class="cpp-keyword">operator</span>++(<span class="cpp-keyword">bool</span>) { iterator tmp = *<span class="cpp-keyword">this</span>; ++index; <span class="cpp-keyword">return</span> tmp; }<br>    iterator&amp; <span class="cpp-keyword">operator</span>+=(<span class="cpp-keyword">int</span> n) { index+=n; <span class="cpp-keyword">return</span> *<span class="cpp-keyword">this</span>; }<br>    iterator&amp; <span class="cpp-keyword">operator</span>-=(<span class="cpp-keyword">int</span> n) { <span class="cpp-keyword">return</span> (*<span class="cpp-keyword">this</span>)+=(-n); }<br>    iterator <span class="cpp-keyword">operator</span>+(<span class="cpp-keyword">int</span> n) <span class="cpp-keyword">const</span> { iterator tmp=*<span class="cpp-keyword">this</span>; tmp+= n; <span class="cpp-keyword">return</span> tmp; }<br>    iterator <span class="cpp-keyword">operator</span>-(<span class="cpp-keyword">int</span> n) <span class="cpp-keyword">const</span> { iterator tmp=*<span class="cpp-keyword">this</span>; tmp-= n; <span class="cpp-keyword">return</span> tmp; }<br><br>    iterator( RingBuffer* buff_, size_t index_ ): buff(buff_), index(index_) {}<br>  };<br>  <br>  iterator end() { <span class="cpp-keyword">return</span> iterator( <span class="cpp-keyword">this</span>, used ); }<br>  iterator begin() {<br>    size_t start = (used &lt; n)?<span class="cpp-number">0</span>:(used-n);<br>    <span class="cpp-keyword">return</span> iterator( <span class="cpp-keyword">this</span>, start );<br>  }<br>};<br><br></pre></div><!–ENDSCRIPT–><br><br>That was amusing.  The idea is that now we can push_back data into the ringbuffer, and you can iterate over the contents of the ring buffer by going from begin() to end().<br>

This topic is closed to new replies.

Advertisement