Journal Articles

CVu Journal Vol 14, #3 - Jun 2002 + Student Code Critiques from CVu journal.
Browse in : All > Journals > CVu > 143 (9)
All > Journal Columns > Code Critique (70)
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: Student Code Critique Competition

Author: Administrator

Date: 05 June 2002 13:15:52 +01:00 or Wed, 05 June 2002 13:15:52 +01:00

Summary: 

Body: 

Prizes provided by Blackwells Bookshops & Addison-Wesley

Please note that participation in this competition is open to all members. The title reflects the fact that the code used is normally provided by a student as part of their course work.

Note that this item is part of the Dialogue section of C Vu which is intended to designate it as an item where reader interaction is particularly important. Readers' comments and criticisms of published entries are always welcome.

Student Code Critique 15: The Question

There is a problem with this code to reverse a given string in place with the use of pointers. It gives a segmentation fault whenever it tries to swap two characters in the string. The fault comes in the line indicated in the following code:

#include <string.h>
void swap(char *a, char *b){
  char tmp;
  tmp = *a;
  *a = *b;  
//this is where the seg. fault comes
  *b = tmp;
}
void reverse(char *str){
  char *a, *b;
  for(int i=0; i<strlen(str)/2; i++){
    a = &(str[i]);
    b = &(str[strlen(str)-i-1]);
    printf("swapping %c and %c...\n",*a, *b);
    swap(a, b);
  }
  printf("the reversed string is: %s\n", str);
}
main() {
  reverse("cheese");
}

The Entries

Solution from Michael Ellis

Having read them for a couple of years, I thought it was about time I had a go at one of these code critiques, so here goes...

The first task is obviously to find out why this code seg faults. A segmentation fault occurs when the processor is asked to read or write to memory that doesn't exist or write to memory marked as read-only. Strictly speaking, the first case is actually a bus-error, but many systems merge these two classes of faults into one.

In this code the reason is pretty obvious in that the test harness is calling reverse() with a constant string: constants are often held in read-only memory, and reversing a section of read-only memory is probably going to be quite hard. Some compilers automagically copy constant strings into writable memory when passed to functions expecting non-constant arguments (such as reverse()) however this is very much a compiler dependant feature (Not one I have ever come across, and it would worry me if I did. Francis). I believe that most compilers will have a switch to enable a warning when such code is compiled, but many will ship with it turned off.

The first task, therefore, is to re-write the test harness to operate with a writable chunk of memory. It is also worth considering the possible ways in which reverse() might fail: what happens if it's passed a NULL pointer, an empty string, a string with an odd number of characters and so on. Testing for the correct handling of a NULL pointer is tricky since reverse() doesn't return any value, so the best we can do is call reverse(NULL) and see whether it seg.faults.

Whenever I write functions like reverse(), I always include a complete test harness inside "#ifdef TEST/#endif". Needless to say, functions like reverse() exist in their own compilation unit (roughly a .c file). For reverse(), my test harness would look a little bit like this (modified to reduce the number of lines):

#ifdef TEST
#include <stdio.h>
int main(void) {
  bool failed = false;
  struct{
    char original[10];
    char const * result;
  } tests[] = {
      { "",        ""},
      { "a",       "a"},
      { "ab",      "ba"},
      { "abc" ,    "cba"},
      { "abcd",    "dcba"}
    };
  for(int test = 0; test < 
    sizeof(tests)/sizeof(tests[0]); ++test) {
    printf("Reversing \"%s\"\n",
         tests[test].original);
    printf("expecting \"%s\"\n",
         tests[test].result);
    reverse(tests[test].original);
    printf("      got \"%s\"\t",
         tests[test].original);
    if(strcmp(tests[test].original,
         tests[test].result) == 0) {
      puts(" Pass.\n");
    } else {
      puts(" Failed!\n");
      failed = true;
    }
  }
  if(failed) puts("\n\nSome tests failed.\n");
  else   puts("\nAll tests passed.\n");
// Test for handling of NULL
  puts("testing NULL pointer handling.\n");
  reverse(NULL);
  puts("Well,  it didn't seg-fault!.\n");
  return 0;
}
#endif

Note that I can add new tests very easily just by extending the test array - a little trickery using sizeof() means I don't even have to update a "number of tests" variable. There is a slight issue with the fixed size of ten characters for the original string that I could get around, but it doesn't seem worth it for a test harness.

Notice also that every variable is initialised as soon as it is declared - this is a nice feature of the new version of C which I have used in the for-loop. Sadly I can't use it in reality as I don't have a C99-compliant compiler, however as the original code uses exactly this technique inside reverse() I guess the student must be richer than my employer! (or uses a C++ compiler. Francis)

While writing the new test harness I started to think about the function signature for reverse():

void reverse(char * str)

I don't like this for two reasons - first, "reverse" sounds like a more general-purpose array reversal function than a specific string-reversal function. Second, the formal parameter name of "str" is reserved by the C-standard, as is every other name beginning str. Next, the formal parameter is a non-constant pointer, but as a general rule I like to make pointer parameters constant so that I have some confidence in their value. If I need to change the pointer, I can always copy it to a local variable inside my function, and being a constant pointer doesn't mean I can't write to whatever it actually points to. Finally, void functions miss out on the important trick shown by many of the standard library functions of returning their calling parameter (e.g. strcat()). This makes the function much easier to use in some cases, for example inside the parameter list of a call to another function like sprintf(). I'd therefore change the function signature to be more like:

char * my_strrev(char * const ptr)

Moving on to the actual code of reverse(), it looks like the code is going to be very inefficient: just a cursory look showed that strlen() would be called at least once for every character in the original string, yet reversing a string doesn't alter its length, so why not call strlen() once and hold the result in a (constant) variable?

Looking further, there are a lot of array subscripting operations, and my experience with embedded systems has taught me the hard way that array subscripting is a very slow operation best reserved for when you need to access elements of an array in a random order. As reverse() only needs to access characters sequentially we would be far better off to use pointers directly, and given the original specification ("reverse a string in place with the use of pointers") which sounds suspiciously like a homework assignment, reverse() probably isn't what the lecturer wanted as pointers are only used to pass two characters into swap().

Obviously we need to access the string from both ends, so two pointers moving in opposite directions along the string are the easiest way to proceed. Initialising the first pointer is easy - it's the formal parameter to the function, but the second pointer will need to be "walked" along the string to find the end. As each character is copied, the pointers are moved closer together by one character each, and the whole string has been reversed when the second pointer reaches or passes the first.

The next thing I spotted was that a helper function called swap() is used to actually exchange two characters in the string. In C99 this could be marked as an inline function, or in C89 it could be implemented as a macro. I have to be honest and say that I probably wouldn't do either in this case: I'd probably but the exchanging directly into my_strrev().

The final thing to hit me was that reverse() doesn't just reverse the string - it also prints it out. I'm going to assume this is debugging added to find out why the original was seg.faulting, however I would have preferred to see this sort of code enclosed in some way to make it obvious, for example a call to debug_printf() or wrapped in "#ifdef DEBUG/#endif".

So what does my_strrev() look like after all these changes?

char * my_strrev(char * const ptr) {
  char * beg = ptr;
  char * end = ptr;
// Handle a NULL pointer gracefully...
  if(ptr == NULL) return NULL;
// Walk the end pointer up to the nul character
  while(*end != '\0') ++end;
// end now points to the nul - step back by one
  --end;
// Now reverse the string until the pointers 
// cross over
  while(beg < end) {
// Swap two characters and move the pointers
    char tmp = *beg;
    *beg++ = *end;
    *end-- = tmp;
  }
  return ptr;
}

You can see that I've actually walked the end pointer one character too far and have had to back it up again. There are a whole host of other ways I could have achieved the same result by adding +1 and -1 all over the code, however I feel this is the most obvious. I've also used post-increment on both of the pointers: normally I'd actually write this as two separate stages (swapping the characters and then moving the pointers) however I know that the number of lines of code needs to be kept down for printing in C-Vu and this code is still pretty readable, especially as the comment highlights the pointers moving.

Fixing the original seg.fault was quite easy. [well implicitly you did, but being picky, you did not do so explicitly. Francis] Doing a code-review of the rest of the functions shows that a lot can be changed, but how much better is the new code? That depends on how you define "better". One metric might be how fast the code is, and fortunately I have access to some really powerful code profiling tools. I ran the same tests on both my_strrev() and reverse(). reverse() took an average of 1.64us, of which only 0.34us was in reverse() itself, the rest being in swap() and strlen(). my_strrev() took an average of 0.43us with no function calls. So was it worth it? Possibly. If my_strrev() is only used once or twice inside a huge application, a saving of 1.21us simply isn't worth the effort. However, if my_strrev() is used in the middle of a tight loop called a hundred thousand times per second in a real-time critical system, saving 121us might make the different between your code being fast enough and crashing. As I spend most of my time in the latter environment, it's probably worth it for me. As the original code was probably a homework assignment, the time saving really isn't worth it. Perhaps this goes to prove the old adage regarding optimisation:

Rule 1 (for all programmers): Don't do it.

Rule 2 (for experts only): Don't do it yet.

Solution from Robert Lytton

There are two issues that need to be addressed in this code; what is 'C' and the use of constant data.

What is 'C'

This my second attempt at this paragraph. I have been having nightmares about Francis' corrections and must try to minimise them before anything gets published! Many compilers today are willing to accept a wide range of code 'standards' in the source files. Some compilers will pass back warnings that the code is not conforming to a certain standard, some accept the source unquestioning, whilst others will refuse to compile it. The two main standards to be aware of are the original 'ANSI C' (ISO/IEC 9899:1990) (now referenced as C89 for clarity) and the new 'C99' (ISO/IEC 9899:1999). So what standard should I adopt? I guess in time I will be using C99 but at present I need the security of being able to move code between different compilers. There are some target processors that may take years to be fitted with a C99 compiler, the likes of lcc are important tools and I know nothing of its future conformity. There are two choices; the chosen compiler will decide the standard or write code that conforms to both the old C Standard and the new 'C99' one. For me the latter is the preferable solution and so the // commenting style is out along with a few other things. Turning to the code, it conforms to neither standard at present, the following steps must be taken:

  1. The function main() always returns an int. In C89 this is the implicit type but C99 requires us to state it. C99 will add an implicit return 0 for us but a return statement is needed by C89. The function arguments, it seems, are optional in both standards… (further investigation needed).

      int main() {
        return 0;
      }
    
  2. In ANSI, the for-loop iterator i must be declared at the start of a block (after curly brackets),

  3. The for-loop iterator i should be of type size_t so that it may be compare with the return value of strlen(),

      {
        size_t i;
        for(i=0; i<strlen(str)/2; i++)
        {
    
  4. To use printf(), the stdio.h header needs to be included,

      #include <stdio.h>
    

The use of constant data

When a compiler creates a binary it will separate the address range into areas for code, constant data and writable data (this is simplified). All these areas may reside in RAM but on some systems constant data and code may reside in ROM or be protected by an MMU from being written to. In the example code the string literal "cheese" will be placed in the constant data area. The compiler is quite entitled to overlay the memory used to store the string literal for other string literals, such as "cheese" and "bigcheese", declared else where in the code. The compiler assumes that this data is un-modifiable and is therefore making the best use of the memory.

When the code tries to reverse the string it uses the original constant data as its work-space. This will either fail due to the data being in ROM, cause the MMU to issue a segmentation fault or succeed, breaking the expectation of the code and compiler! The last scenario is very dangerous, the next time we come to use the expected constant value "cheese" we will in fact be using "eseehc".

The problem may be simply solved by 'moving' the string from constant storage into writable storage, viz:

  char word[] ="cheese";
  reverse(word);

The string literal "cheese" will still be compiled into the constant data area but on calling the function main(), the data will be copied onto the stack and pointed to by the variable word. word's type informs the compiler that the object it is pointing to is non-constant.

It would be nice if the compiler stopped us when we were trying to write to constant data. The problem is that C allows us to pass a const char* as a parameter to a function that expects a non-const char*

[not on any compiler I have used. The problem is that some Standard C Library functions return a plain char* which is based on a char const * parameter. Francis].

  const char word[] ="cheese";  
  reverse(word); /* word is of type const
                 char* */

In C++ this should cause a compile time error, where as in C you may get a warning. To compile the C++ code, or just remove the warning, you would have to change reverse() to accept a parameter of type const char*. Inside reverse() you would then have to change variable a and b to type const char* and also the function swap() to take const char* parameters. The function swap() would then cause a compile time error when you tried to write to the de-referenced const char*. This is exactly what you want to happen, because it is an error!

[Note that as far as standards are concerned 'warnings' and 'errors' both comply with any requirement for a diagnostic. Francis]

There is another way around the problem of the C++ compiler refusing to compile buggy code or just to remove the compiler warnings in C. The constant data may be cast to be non-constant, viz:

  const char word[] ="cheese";
  reverse((char*)word);

If you need a cast to make something compile without error or warning... then think again!! [yes, if you lie to the compiler the resulting mess is entirely your fault. Francis]

In C, string literals are specified to be un-modifiable. However, string literals do not have the type const char[] (thereby allowing them to be passed to library functions that take char*). In C++ it also seems that the constness of string literals is not revealed to the compiler and hence a C++ compiler will compile reverse("cheese") without an error and possibly without a warning. [But if you overload reverse to provide a version for arrays of const char it will select that one. Francis]

The lessons to be learnt are:

  • Use string literals with care;

  • Use the const qualifier more rigorously;

  • Heed compiler warnings on constness.

The corrected code is presented below.

I have taken the opportunity to remove the overhead of several calls to strlen(). I decided not to inline swap() at this stage but have made it available only to functions in this file by adding the keyword static. I have taken the precaution of initialising the pointers a and b in reverse() to zero. By always initialising pointers, you are more likely to discover bugs caused by de-referencing pointers that would otherwise be randomly initialised. A more effective step would have been to reduce the scope of a and b by placing them inside the for-loop and then initialise them with their final values, viz:

for(;;){
  char *a = &(str[i]);
  char *b = &(str[len-i-1]);

Finally it is always wise to check if you are re-inventing the wheel and in this case someone has already gone to the effort of writing the code for you. The function strrev(), found in string.h, [Only if you are using an extended Standard C Library as that function is not supported by the Standard. Francis] may be used in place of reverse() and swap(). However the need to 'move' the string literal into writable memory still exists. If we were using C++ we could have used a string type, protecting us from the problems associated with string literals.

#include <string.h>
#include <stdio.h>
static void swap(char *a, char *b){
  char tmp;
  tmp = *a;
  *a = *b;
  *b = tmp;
}
void reverse(char *str){
  char *a=0, *b=0;
  size_t i, len=strlen(str);
  for(i=0; i<len/2; i++){
    a = &(str[i]);
    b = &(str[len-i-1]);
    printf("swapping %c %c...\n", *a, *b);
    swap(a,b);
    printf("the reversed string is: %s\n", str);
  }
}
int main(){
  char word[] = "cheese";
  reverse(word);
/* reverse(word) may be replaced by strrev(word) */
  return 0;
}

Solution from Victoria Catterson

The Problem

The fundamental problem with this program is that the parameter passed to the reverse function is a string literal. Also called a string constant, a string literal is an array of characters enclosed in double quotes, in this case "cheese". Attempting to change the contents of a string literal results in undefined behaviour[1].

In this program, the string literal is being modified by the function swap, which attempts to swap two characters, the addresses of which are passed as parameters. The first time swap is called, the addresses of the first and last characters (c and e) of the string are the parameters. The line:

*a = *b;

attempts to overwrite the character 'c' with the character 'e', thus altering the string literal. It is at this point that the segmentation fault occurs.

There is a simple solution to this problem: do not pass a string literal to the reverse function. Instead, declare a character array with "cheese" as its contents, and then pass the address of the array to the function. The main function then becomes:

main () {
  char food[] = "cheese";
  reverse (food);
}

It should be noted that "cheese" is still a string literal, but it is being used to initialise the array food in this version. It is therefore the characters of food that are being modified by swap, and not the string literal itself. This is an important point to understand, as the following declarations are completely different.

char hunk[] = "cheese";
char *slice = "cheese";

The first allocates an array of char, of size (strlen ("cheese") + 1), and copies the characters of the string literal to the characters of the array. The second merely sets a pointer to char to the address of the string literal. Passing hunk to reverse is fine, because it is the contents of the array that are modified. Passing slice is illegal, because the code will attempt to modify the string literal itself.

Other Comments

While the string literal was the major problem with the program, there are a couple of additional points to address.

The main function must be defined as returning int. As there is no return type explicitly stated for this main, the default return type of int applies (But that is no longer true in the latest C Standard, where support for implicit int has been removed Francis). However, no value is returned at the end of the function. The value returned by main can be used to determine if any errors occurred in execution of the program [More correctly, the value returned by main is passed to exit(). C programmes run as if you had invoked them with exit(main()) Francis]. By convention, a zero return value indicates that the program ran successfully.

Additionally, main is always defined as either int main (int argc, char **argv), or int main (void). In this case, no arguments are required and so void should be used. Omitting the void is old-style C, and not ANSI C.

The student has used the C++ or Java style comment, beginning the line with "//". C comments are enclosed within slashes and stars[1], i.e.:

/* This is a C comment */ 

[but see below. Francis]

This, in addition with the previous point, suggests that the student learned C++ or Java before learning C. If this is the case, then particular note should be made of these points, as the student is likely to repeat them from habit otherwise.

In the reverse function, strlen is called more than once for the same string. If the length of the string will not change, it is better to call strlen only once, and save the result in a variable for reuse. Efficiency is not an issue with this program, but it is a good idea to make simple optimisations where possible, as long as it is not at the expense of clarity. In this case, strlen is called twice in every iteration of the loop, which is inefficient and no more clear than calling it once before the loop, saving the result to a variable, and using the variable in place of the strlen calls.

The standard library function printf is called in the reverse function, and so stdio.h must be included, in addition to string.h.

Some of the identifiers chosen could be better. The variable name str is fine, but it is worth noting that all identifiers beginning with "str" followed by a lowercase letter are reserved[2]. Therefore names like string should be avoided. In addition, the function named reverse reverses the string, displaying each swap operation as it is carried out, and then prints out the reversed string. As the name only describes half the functionality of the function, either the name is wrong or the function does too much. In this case, I would suggest moving the call to printf to the main function, so reverse only reverses the string. In general, a function name should describe what it does, and the function should do as little else as possible.

Corrected Version

#include <stdio.h>
#include <string.h>
void swap (char *a, char *b){
  char tmp;
  tmp = *a;
  *a = *b;
/* this is where the seg fault used to come */
  *b = tmp;
}
void reverse (char *s){
  char *a, *b;
  int i;
  int len = strlen(s);
  for(i = 0; i < len/2; i++){
    a = &(s[i]);
    b = &(s[len-i-1]);
    printf("swapping %c & %c...\n", *a, *b);
    swap(a, b);
  }
}
int main(void){
  char food[] = "cheese";
  reverse(food);
  printf("the reversed string is: %s\n", food);
  return 0;
}

My Solution

#include <stdio.h>
#include <string.h>
void reverse(char *s){
  int i, j;
  char temp;
  int len = strlen (s);
  for (i = 0; i < len/2; ++i) {
    j = len-i-1;
    temp = s[i];
    s[i] = s[j];
    s[j] = temp;
  }
}
int main(void){
  char food[] = "cheese";
  printf("The string before reversal is: %s\n", food);
  reverse(food);
  printf("The reversed string is: %s\n", food);
  return 0;
}

Notes on My Solution

I did not think there was anything to be gained from having a separate swap function. The reverse function is relatively small, and does not lose clarity by containing the code that performs the swap. However, making it a separate function would slow the program down, as the overhead of a needless function call would be incurred every iteration of the loop.

Also, I added a second printf call to the main function, to let the user know what the string is before it is modified. This gives the user slightly more information about what the program is doing, which generally makes a program easier to use.

[1] The C Programming Language by Brian Kernighan and Dennis Ritchie, 2nd Ed. Section 5.5: Character Pointers and Functions, page 104

[2] ISO/IEC 9899:1999 subclause 7.26.10 and subclause 7.26.11

Solution from Tony Houghton

The problem with this code was quite easy to spot for a seasoned C programmer; most would get the compiler to spot it for them by enabling warnings (in gcc's case -Wwrite-strings). The problem is that the author is trying to apply reverse() to a string literal, while the implementation treats literals as const and has write-protected the memory it's stored in.

The moral is that the first thing to do when learning to use a new compiler is find out how to enable as many warnings as possible. I really think compilers should enable all warnings by default and let advanced programmers disable them if necessary (for legacy code etc), instead of the other way round.

An easy fix is to use strdup() to create a writable copy of the string. However, strdup() is not strictly standard, and not always provided. Luckily it's very easy to implement, so I've written an implementation called str_dup(). Most names beginning with "str" are reserved by and for the standard, but that only applies to those where the "str" is followed by another lower-case letter.

I want to keep reverse()'s implementation of altering the input string, as I'll discuss later, so I'll put the call to str_dup() in main() for now. So the implementation of main() becomes:

reverse(str_dup("cheese"));

The code now compiles without the warning about a discarded qualifier, and more importantly runs and produces the expected result instead of seg-faulting. However, as we've been sensible and enabled other warnings as well, we see that printf() needs declaring, main's declaration is wrong, and it needs a return value. We should also be tidy and free the temporary string we've created. So we add some includes (stdio.h for printf(), stdlib.h for free() and the malloc() in our strdup implementation) to the top of the source file, and main() becomes:

int main(void){
  char *str = str_dup("cheese");
  reverse(str);
  free(str);
  return 0;
}

I recently embarrassed myself on a newsgroup about the definition of main: if you're not using arguments, I learnt the correct declaration is int main(void) rather than int main(). [But only in C. Francis]

I don't see a need to change swap(); there are a number of changes I'd like to make to reverse() though. I'll keep the implementation that it alters the string it's passed, because there'll often be times when this is the desired behaviour for performance reasons, rather than creating a new string. But it's common practice to return a copy of the pointer to the string in such cases, for convenience and to enable tail-call optimisations. The name reverse() is fine, because it's obvious by the argument type that it's a string we're reversing. The name "str" for the argument is fine, not being followed by a lower-case letter.

To make reverse() more versatile, we don't want it to print the result, nor the messages for debugging swap if we want to use it for real. The best thing to do with the final printf is move it into main(). For now the debugging messages can be sent to stderr instead of stdout; then they can be easily be suppressed or redirected to a file independently of the output we really want.

Working down the reverse() function, a and b are declared earlier than necessary, so we can move that inside the loop and combine the declarations with assignments. We could do without the variables altogether and use expressions, but with the debugging statement it's more convenient to keep them, and even if we remove the debugging, the use of temporary variables won't cause any run-time overhead with a modern compiler. I prefer the syntax of pointer arithmetic to logically dereferencing with [], then taking the address again, so I've changed that in the assignment statements too.

I've moved the declaration of i outside the loop, because I've got into the habit of trying to make my code portable, and ANSI C is still much more widely supported than C99. It should also be size_t instead of a simple int. There's a strong case for using strlen() in each instance that the length of a string is needed, but I've decided to call it just once and store the result in a variable here. This is because we're not changing the length of the string, but as we are changing the content, the compiler might decide it isn't safe to optimise it away to one call, and performance would suffer, especially as it's used twice each time round the loop.

Finally, as we wanted to use reverse() on, effectively, a const string, it would be useful to provide an alternative version of the function that operates on and returns a copy. I'll call this version reverse_copy(); it's a very simple forwarding function, so if I were still using C99 it would be a candidate for inlining. Now all that remains is to alter main() to use reverse_copy() instead of strdup() and reverse().

At the risk of going on too long for such a short piece of code, there is one more thing that can be done. So far we've stuck to a single source file for this example. The functions are all potentially useful though, so it would be a good idea to put them in a separate file from main so that it can be built into a useful library for use in other programs in future. With this in mind, I'll go back on what I said before about keeping the function names and give them all a prefix of str_ to indicate they're user-supplied code to supplement the standard library's string operations. It's good practice to decide on a prefix for each source file in a large program and use it religiously on every function name and non-local variable: it aids debugging and reduces the chance of name clashes.

Here is the code. Where a function has a comment next to its header declaration I've saved space by not providing a similar comment in the source file:

#ifndef STR_EXTRA_H
#define STR_EXTRA_H
/* Used instead of strdup() in case the latter isn't provided by the platform */
char *str_dup(const char *);
/* Swaps the characters pointed to by a and b */
void str_swap(char *a, char *b);
/* Reverses str and returns a copy of the pointer (not a copy of the string) */
char *str_reverse(char *str);
/* Returns a reversed copy of str, which may be free()'d */
char *str_reverse_copy(const char *str);
#endif /* STR_EXTRA_H */

/* str_extra.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *str_dup(const char *str){
  char *ret = malloc(strlen(str) + 1);
  strcpy(ret, str);
  return ret;
}
void str_swap(char *a, char *b){
  char tmp;
  tmp = *a;
  *a = *b;
  *b = tmp;
}
char *str_reverse(char *str){
  size_t i;
  const size_t len = strlen(str);
  for(i = 0; i < len/2; ++i)  {
  char *a = str + i;
  char *b = str + len - i - 1;
  fprintf(stderr, "swapping %c & %c...\n", *a, *b);
  swap(a, b);
  }
  return str;
}
char *str_reverse_copy(const char *str){
  return reverse(str_dup(str));
}
/* End of str_extra.c */
/* main.c */
#include <stdio.h>
#include <stdlib.h>
#include "str_extra.h"
int main(void){
  char *str = reverse_copy("cheese");
  printf("the reversed string is: %s\n", str);
  free(str);
  return 0;
}
/* End of main.c */

And the Winner

While I am happy to run this column for a while longer (the most tedious part is formatting everything) I think it should be the Editor to choose the winner. So over to James.

I have chosen Michael Ellis, even if he has a slightly performance driven perspective, because of the clarity of what he writes, and the emphasis on testing and avoiding pointless optimisations, even if he does miss out on the minor trick that I think "str" is not technically reserved as a name -- it ought to be avoided anyway.

James

OK Mike, email me () with your choice of book (under £40) and I will arrange for you to get it.

Francis

Student Code Critique 16

This one is a little bit different. Prompted by the emails for help that Bjarne Stroustrup showed me, I want you to look at the fillowing very simple piece of code. The problem is obvious, but saying that is not going to help the student. We need to change his mental model so that it is obvious to him too. So write a response to the student. In the response you should try to identify the causes of this kind of misunderstanding so that the student will have a clear idea of the differences between a declaration, definition and call of a function. You may think this is easy, but if it is, why do so many students fall into this kind of trap? (Oh and there are a couple of ordinary bugs in there as well, which should not be ignored.)

#include<stdio.h> 

void count_up(int start, int end, int step){
  printf("enter the upper limit");
  scanf(%d; &start)
  printf("enter your lower limit");
  scanf(%d; end);
  printf("enter step size");
  scanf(%d; step)
  int x;
  for(x=start; x<=finish;x=x+step) {
    printf("\n %d",x);
  }
}

int main(){
  count_up(1, 10, 1);
  count_up(-10, 10, 2);
  return 0;
}

I want to give everyone a chance to enter, but please try to get your entry in as early as possible and anyway not latter than July 16.



[1] This is not entirely accurate, as the student's version is supported by the C99 standard [for both late declaration of variables and for // style comments]. However, as C99 is not widely used, it is more likely the student is required to write C89 compatible code. The student's version is invalid C89 code. [It is even more likely that the student's instructor does not know the difference. Francis]

Notes: 

More fields may be available via dynamicdata ..