3

It has been a while since I looked at C (still learning) and I just got back into the K&R book.

I just had a go to Exercise 5-3 (p107).

Write a pointer version of the function strcat that we showed in Chapter 2: strcat(s,t) copies the string t to the end of s.

I came up with this...

void strcat(char *s, char *t);

void strcat(char *s, char *t) {

    while (*s++ != '\0');
    s--;
    while (*t != '\0') {
        *s++ = *t++;
    }

    *--t = '\0';

}

int main() {
   char str[] = "Hey, hello";
   char str2[] = " are you?";

   strcat(str, str2);

   printf("%s\n", str);

   return 0;

}

It seems to work.

What I am wondering, is that the K&R book often writes exercises with as little lines as possible - I'd expect had they provided their own code sample for above, you'd get things like this...

void strcat(char *s, char *t) {

    while (*s++ != '\0');
    s--;
    while ((*s++ = *t++) != '\0');
    *--t = '\0';

}

To me, this is less readable (maybe this example isn't as good, but I often look at their code and think if that was separated into a few lines, I'd understand it much better). The examples provided in the book seem to advocate this sort of assignment in the condition part of a loop, and in fact cramming as much code as possible per line.

Is the book right in trying to do as much possible where you can, even if readability suffers?

Is this just The C Way?

alex
  • 479,566
  • 201
  • 878
  • 984
  • 6
    the K&R book was written like 25 years ago (32 to be precise). Things have moved on... – Mitch Wheat Oct 16 '10 at 10:23
  • 2
    If you've ever had to write programs using a teletype or punched cards then you would value terseness - these days of course it's much more important to aim for clarity rather than succinctness. – Paul R Oct 16 '10 at 10:26
  • C was among the first languages I learnt. BUT, why learn C? You'd be better off learning C# – Mitch Wheat Oct 16 '10 at 10:26
  • @Mitch Wheat I wanted to know a low level language, for my own purposes (not professional). I think I'll get to C# one day. – alex Oct 16 '10 at 10:28
  • OK, so if the answer is *only for historical reasons*, someone should post it (bonus points for critiquing my code :) ) – alex Oct 16 '10 at 10:30
  • 7
    @Mitch Wheat: Yes, no-one uses C in a professional environment. – Oliver Charlesworth Oct 16 '10 at 10:35
  • 4
    @Mitch: C and C# are really applicable to two very different problem domains - you probably wouldn't want to use C# for embedded systems or real-time applications. – Paul R Oct 16 '10 at 10:36
  • @Oli Charlesworth, Paul R: Yes. I know both and use both. I never said that no-one uses C in a professional environment; neither did I even suggest that C# should be the language of choice for embedded (though I do use it for just that). But I am saying the poster will probably put those C# skills to more use...and I'd be surprised if that wasn't obvious... – Mitch Wheat Oct 16 '10 at 10:40
  • 1
    @Mitch I'd agree with C# being more useful, but I've spent all my time with high level languages so want to know more about what those high level functions actually may be doing. Thanks for the recommendation. – alex Oct 16 '10 at 10:43
  • 4
    @Mitch: I guess my implied point was that making sweeping assertions that "you'd be better off learning C#" without knowing anything about the OPs situation or background is somewhat presumptuous! – Oliver Charlesworth Oct 16 '10 at 10:52
  • 1
    Btw, it doesn't look like you need that last line: `*--t = '\0';` Furthurmore, since that book was written it has become a best practice to never write blindly to a pointer. Always check that pointers are not NULL and pass a maximum buffer length then stay within it. – Amardeep AC9MF Oct 16 '10 at 10:52
  • @Amardeep Ah yes, of course! It should already be null terminated. Thanks :) – alex Oct 16 '10 at 10:55
  • @Oli Charlesworth: I do not think that is a sweeping assumption. 99 times out of 100, today, you'd be better off learning C#. – Mitch Wheat Oct 16 '10 at 11:02
  • 6
    @Mitch: Don't want to go into an off-topic debate here, but I will say that I respectfully disagree; I think the ratio is significantly lower than you claim. If you need to understand what's really happening under the hood, you should learn C. Ditto if you need to do embedded development. Ditto if you need to do kernel development. Ditto if you need to work with legacy systems. Ditto if you're on a platform that doesn't support .NET or Mono. I'm sure there are other reasons. (Disclaimer: I've used (and quite liked) C# in the past.) – Oliver Charlesworth Oct 16 '10 at 11:17
  • The 2 lines that are not `while` loops in your `strcat` should be removed. The first one is a bug and the second is redundant. – asveikau Oct 17 '10 at 06:20
  • @asveikau: The first you mention (s--) is not a bug. – Fred Nurk Dec 08 '10 at 07:56
  • `char* strcat(char *dest, char const *source) { strcpy(strchr(dest, '\0'), source); return dest; }` – Fred Nurk Dec 08 '10 at 08:02
  • 1
    @Mitch: Complete environments like GTK are written in C and used in profesional environment. That's different to "no-one". – harper Feb 01 '11 at 12:03

3 Answers3

13

K&R explain the importance of idioms in the book. Yes, brevity of code is valued by C programmers, but it's not deliberately terse to punish beginners. After some time reading and writing C you start to recognize patterns, so when you see them in someone else's code you know what you're looking at.

Go through the iterations of strcpy() given as an example in K&R -- they explain their philosophy of brevity vs. clarity, and talk about idioms.

gregjor
  • 21,802
  • 1
  • 23
  • 16
5

You should not expect your program to work, since you are invoking undefined behavior.

You define two buffers of a certain size (str is 11 bytes long, str2 is 10 bytes long). Then, during strcat, you try to write to str[11], which doesn't exist. From this point on there is no guarantee whatsoever about the execution of your program. It may crash, it may do what you expected, or it might just print "42" and make you wonder why.

Furthermore, you should not change *t in strcat, since in newer versions of C t has type const char *.

And third, when re-implementing a function that is also provided by your environment, give it another name. Otherwise your compiler might replace it with some builtin code that is equivalent to the function call. For example GCC has __builtin_strlen which sometimes replaces calls to strlen.

The fixed version of the code looks like this:

#include <stdio.h>

/* renamed strcat to str_cat to avoid confusing the compiler */
void str_cat(char *s, const char *t) { /* added the const qualifier to t */

    while (*s++ != '\0');
    s--;
    while (*t != '\0') {
        *s++ = *t++;
    }
    /* removed the needless modification of *t */
    *s = '\0'; /* edit: added this line after the comment from Jonathan Leffler */
}

int main() {
   char str[80] = "Hey, hello"; /* note the large array size here */
   char str2[] = " are you?";

   str_cat(str, str2);
   printf("%s\n", str);

   return 0;

}
Roland Illig
  • 40,703
  • 10
  • 88
  • 121
  • You're right that `*--t = '\0';` should not contain the decrement; but you do need to null terminate the string with `*t = '\0';` because the loop does not copy the NUL. – Jonathan Leffler Oct 16 '10 at 14:41
  • 1
    Maybe the code was meant to be `*s = '\0'`. I still don't see a reason to modify anything related to `t`. When `strcat` is called, `t` is already assumed to point to a string, which (by definition) includes that it is NUL-terminated. – Roland Illig Oct 16 '10 at 14:55
0

Other more readable, more efficient examples can be found by using Google Codesearch.

Look at the source code for Android and BSD in particular as good examples of more modern C implementation of strcat.

Instead of strcat you should be writing an implementation of strlcat and many examples of that source can be found as well.

dawg
  • 98,345
  • 23
  • 131
  • 206