Journal Articles

CVu Journal Vol 13, #2 - Apr 2001 + Programming Topics
Browse in : All > Journals > CVu > 132 (14)
All > Topics > Programming (877)
Any of these categories - All of these categories

Note: when you create a new publication type, the articles module will automatically use the templates user-display-[publicationtype].xt and user-summary-[publicationtype].xt. If those templates do not exist when you try to preview or display a new article, you'll get this warning :-) Please place your own templates in themes/yourtheme/modules/articles . The templates will get the extension .xt there.

Title: Gotcha(); // describe some bugs

Author: Administrator

Date: 03 April 2001 13:15:44 +01:00 or Tue, 03 April 2001 13:15:44 +01:00

Summary: 

Body: 

I am not a language lawyer, although I greatly admire people who are. One thing I do seem to be good at is writing buggy code. I am constantly amazed at how hard I find it to write correct code. Sometimes the code does what I meant it to, but it's not correct because I just didn't understand the problem domain well enough. And sometimes the code just doesn't do what I meant it to. I thought I would share some of the latter accidents in the hope that someone would either learn something about C/C++, or derive some amusement from my ignorance. Feel free to Laugh Out Loud.

Delusion

Recently I wrote code something like the following, and was surprised when it didn't work:

void zeromac(unsigned char mac[6]){
  memset(mac, 0, sizeof(mac));
}

When I say it didn't work I mean it compiled without warnings but when run it didn't do what I meant it to. What I wanted was to write zero into each byte of the given 6-byte buffer. What it actually did was to write only 4 zero bytes. It may be obvious to you what is wrong with this, but when I wrote it, it seemed like a reasonable thing to do as I was used to writing code such as the following, which does work:

unsigned char mac[6];
memset(mac, 0, sizeof(mac));

Had I written the parameter as a pointer I would not have used sizeof:

void zeromac2(unsigned char *mac){
  memset(mac, 0, 6);
}

In fact in the original function I did declare the parameter as a pointer, I had just confused myself by using the array declarator syntax. So in the original function when I wrote sizeof(mac) I was getting the size of a pointer to a 6-byte array, and on my machine the size of a pointer to something is 4. I consider myself lucky that the size of the object I was trying to zero was different to the size of a pointer. Had they been the same the code would have worked by accident and my delusion would remain.

You might think that the good thing about the original function is that the compiler can check that the function is called with objects of exactly the right size. (I did.) You might expect to get a compile time error if you tried to do something like this:

unsigned char m[3];
zeromac(m);  // compile time error?

But you wouldd be disappointed. So long as you pass zeromac() a pointer to an unsigned char you won't get a compile time error. One way to clarify the code and avoid the magic constant 6 in zeromac2() would be to use a typedef:

typedef unsigned char mac_t[6];
void zeromac3(mac_t mac){
  memset(mac, 0, sizeof(mac_t));
}

Here sizeof(mac_t) is 6, so that's OK (sizeof(mac) is still 4). But you can still pass any unsigned char pointer to zeromac3(). One way you could get the compiler to warn you of such mistakes is to use a reference:

void zeromac4(unsigned char (&mac)[6]){
  memset(mac, 0, sizeof(mac));
}

or

void zeromac4(mac_t &mac){
  memset(mac, 0, sizeof(mac));
}

In this case mac is a reference to an array of 6 unsigned chars and sizeof(mac) is the size of the referenced object, i.e. 6, which is correct. Also any attempts to call zeromac4() with other than an array of 6 unsigned chars will cause a compile-time error, which is reassuring. In zeromac4() because mac is a reference it will never be a null pointer, which it might be in the first three functions.

I am sure there are many other ways to design a solution. One obvious way would be to define a class to represent this data. Say:

class Cmac{
public:
  void SetZero(){
    memset(m_data, 0, sizeof(m_data));
  }
  // other stuff
private:
  unsigned char m_data[6];
};

Then I could have safely written:

Cmac mac;
mac.SetZero();

Led Astray

More than ten years ago I was got by a bug, which for some reason has stuck in my mind. I don't remember the details of the program but the bug occurred in a table of initialised data. Let us say the table looked in part like this:

{"cow", 2, 3, "knife"},
{"cat", 192, 160, "fork"},
{"dog", 43, 21, "spoon"},

I find tables of data where the columns don't line up very hard to read, so I thought I would neaten the code like this:

{"cow", 002, 003, "knife"},
{"cat", 192, 160, "fork"},
{"dog", 043, 021, "spoon"},

After all, leading zeros are ignored when reading a number. Aren't they? Well the program didn't work in this neatened form and I know now of course that "A leading 0 (zero) on an integer constant means octal; a leading 0x or 0X means hexadecimal." [KandR1988] I didn't own a copy of K&R at the time (I seem to remember it cost something like £40 then and I was too tight to buy myself a copy.) I was learning C by using C. I don't think I would have mistakenly prefixed any numbers with 0x, but zero alone seemed safe enough. Now it looks like a stupid thing to do, why not use white space?

{"cow",   2,   3, "knife"},
{"cat", 192, 160, "fork"},
{"dog",  43,  21, "spoon"},

The Interview

This last gotcha is not really about a bug that got me, but it is a story about my lack of knowledge of C++, and I did feel caught out. A couple of years ago I was asked a question at a job interview that I couldn't fully answer. The interviewer wrote three lines on a board, something like this:

const char *p;
char const *p;
char * const p;

Then he asked me to describe what was being declared. I told him that the first line declared that p was a pointer to a constant char, in other words p could be modified but what p pointed to could not. E.g. ++p was permitted but ++(*p) was not.

I told him that the last line declared that p was a constant pointer to a char, in other words p could not be modified but what p pointed to could be. E.g. ++p was not permitted but ++(*p) was.

But I didn't know what the middle line declared. I wasn't sure whether the const referred to the char or the pointer or was incorrect syntax or what. I have always found declarations one of the trickiest parts of C++. (I wrote a short piece about declarations which was printed in the July 2000 C Vu). At the time of the interview I had been working with C and C++ for about ten years, but I hadn't come across declarations in the form of the second line before.

Actually the second line is valid and is semantically identical to the first in both C and C++. In these first two declarations the base type, char, is qualified by const and it doesn't matter whether the qualifier comes before or after the type. In fact I think it is a little more consistent to put the qualifier after the type as then the second and third declarations may be read consistently right to left: (2) p is a pointer to a constant character, (3) p is a constant pointer to a character. On the other hand (1) p is a pointer to a character constant also makes some sense, so perhaps it doesn't matter much.

My interviewer could have completed the set by asking me to describe

char const * const p;

(p is a constant pointer to a constant character), but he didn't. On the other hand it's interesting to wonder why he asked me about these declarations at all; after all he would have seen from my C V that I'd been writing C and C++ for a decade. Did he think I was lying about my experience or did he think I might not have come across declarations such as these in my career? And yet, although I thought of myself as a moderately experienced programmer, he had uncovered a fairly basic gap in my understanding. On the other other hand so what? Because I didn't know a qualifier could be placed after a type I would not have written code that way. And had I needed to work on someone else's code containing declarations with qualifier after type I would have learnt what this meant in pretty short order.

I didn't get the job (probably for some bigger gaffs than this). C'est la vie.

Thanks

I'd like to thank Kevlin Henney for reading a draft of this article. Any errors that remain are my own.

Reference

[KandR1988] Brian Kernighan & Dennis Ritchie The C Programming Language Second Edition, Prentice Hall, 1988.

Notes: 

More fields may be available via dynamicdata ..