views:

2027

answers:

13

I was asked this question in the MS written walkin-interview:

Find errors in the program below which is supposed to to return a new string with \n appended to it.

char* AddnewlinetoString(char *s)
{
  char buffer[1024];
  strcpy(buffer,s);
  buffer[strlen(s)-1] = '\n';
  return buffer;
}

Thank you all.

I'd tried to code on myself and was able to get it working by making buffer variable global and having buffer[strlen(s)] = '\n'. But did not know there were many other bugs in it.

Appreciate your time.

+45  A: 

I can see a few:

Length of input string not checked.

What if the strlen(s) > 1023? You can fit a string of length at most 1023 in buffer.

Overwriting the last char with \n

You are overwriting last char with newline. Your \n should go where \0 used to be and you need to add a new \0 after \n

Variable buffer is local to function and you are returning its address.

Memory for buffer is allocated on stack and once the function returns, that memory is freed.

I would do:

char* AddnewlinetoString(char *s) {

  size_t buffLen = strlen(s) + 2; // +1 for '\n' +1 for '\0'
  char *buffer = malloc(buffLen); 
  if(!buffer) {
   fprintf(stderr,"Error allocting\n");
   exit(1);
  }
  strcpy(buffer,s);
  buffer[buffLen-2] = '\n';
  buffer[buffLen-1] = 0;
  return buffer;
}
codaddict
Funnily enough, the `\0` is probably still there (if the first error doesn't trip it). Look again, is the `\0` overwritten?
Muhammad Alkarouri
2. `strlen` doesn't include the terminator in the length. So at most, the code just replaces the last char of the string with a newline, but the nul will remain if it already was there.
cHao
@Muhammad,@cHao: Thanks for pointing.
codaddict
I'd use size_t to hold return value from strlen()...
Nyan
"Memory for buffer is allocated on stack and once the function returns, that memory is freed." It's a long, long time since I did any c++ but I thought that as long as the memory was referenced by a pointer (and not explicitly deallocated) it would remain allocated ( or is that me getting java in my c++)
TygerKrash
@TygerKrash: The stack holds values local to the current function. Once the function returns, your pointer is pointing to garbage, i.e. you don't know what's there (e.g. the next function's local variables can overwrite the region your pointer points to).
3lectrologos
@Nyan: Thanks for pointing. Changed `int` to `size_t`
codaddict
+1 - I would rephrase to say "once the function returns, the memory is out of scope" - allocated and freed are loaded terms in the context of malloc etc. Distinguish more clearly between heap and stack memory usage.
Steve Townsend
This will leak memory if used like: `str = AddNewlineToString(str);`
sje397
@sje397: That's not a problem with the function, provided it is properly documented that the caller owns the returned buffer, and is responsible for freeing it appropriately. The only alternative is to work with a global buffer, which has much more severe issues. *edit: Given that you're stuck with a C-like interface, of course.
Dennis Zickefoose
@Dennis Zickefoose: lack of docs (or comments) could be considered a problem. Another alternative would be to free the input pointer (yeah, not good). Anyway my point is that it was doomed from the moment the signature was written ;)
sje397
haha why does this have upvotes?
Jonathan
'Twould not be a bad idea to make the function argument const.
Jonathan Leffler
This crashes if you pass null as the argument.
Tony Lambert
Noone has mentioned yet that the original function fails if you pass it an empty string (it accesses s-1 in that case)
nos
@Tony: nothing wrong with that. It will also crash (or worse) if you pass `(char*)rand()` or any other invalid pointer. Why should NULL be treated specially?
R..
+9  A: 
  1. there's no limit in strcpy, better use strncpy.
  2. you are copying to static buffer and returning pointer.
Tomasz Kowalczyk
Surely that's not a static buffer?
unwind
3. Buffer overflow
Filip Ekberg
Instead of `strncpy` one should make sure the buffer is of suitable size. Exactly what good is it doing if you get the input truncated, rather than having newline appended? This should take care of both issues.
visitor
If available, strlcpy would be better.
Matteo Italia
+4  A: 

3 things

   int len = strlen(s);
   char* buffer = (char*) malloc (len + 2);   // 1
   strcpy(buffer,s);
   buffer[len] = '\n';           // 2 
   buffer[len+1] = '\0';         // 3
   return buffer;

Edit: Based on comments

aeh
Calling `strlen()` three times instead of using a temporary might lead to disappointing results.
sharptooth
@sharptooth: Have updated!!
aeh
@aeh: strlen doesn't count the terminating '\0', so actually it replaces the last char before the '\0' and fails for empty strings that contain only the termination.
Secure
@aeh: your answer contains a bug. You simply return a new string with the last character of s replaced by `\n`. len is one character too small.
JeremyP
You don't check for `s` being `NULL` and for `malloc()` to fail.
Georg Fritzsche
they would also like `char* AddnewlinetoString(const char *s)`
ruslik
@Georg: there is no legitimate reason to check for `s` being NULL. Would you also like to check it against the billions of other invalid pointer values?
R..
@R..: Right, that should be covered by documentation and possibly an assert.
Georg Fritzsche
+7  A: 

Here is a C++ version without errors:

std::string AddnewlinetoString(std::string const& s)
{
    return s + "\n";
}

And here is how I would probably write that in C++0x:

std::string AddnewlinetoString(std::string s)
{
    return std::move(s += "\n");
}
FredOverflow
Doesn't answer the question though :)
Filip Ekberg
True, but... I don't think the point of these exercises is to come up with a better version *per se*, but to understand the intricacies of the errors. Consider it a test of debugging your mediocre colleagues' work, rather than a test of writing new code yourself.
Andrzej Doyle
The question is tagged C++, that would imply this is a potential correct answer!
AshleysBrain
@Fred - Better to pass by ref to const. What comments would you make about exception handling? This is an interview q remember - not making errors is only half the battle. I agree w Filip that you would also have to answer the question as stated, in an interview situation. Perhaps this is for C work.
Steve Townsend
@Steve: I added your suggested reference to const version, but I still like the [pass by value](http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/) version better, especially with the `std::move` edited in :)
FredOverflow
Shouldn't the C++0x version return an rvalue reference? What's the point of using `move` if you return by value? [I admit, I'm an rvalue references noob, so maybe I'm wrong]
James McNellis
@James: No, returning rvalue references is almost always wrong, because it can lead to a subtle problem with dangling rvalue references at client side. In this case, there is another obvious problem: `s` is destroyed when the function returns, so it would be a very bad idea to return an rvalue reference to it. The job of `std::move` is to *enable* moving by treating the argument as an rvalue, thus allowing the move constructor do have its way with `s` when leaving the function. Without the `std::move`, the copy constructor would initialize the return value, because `s += "\n"` is an lvalue.
FredOverflow
@Fred: That's what I thought (concerning making the return type an rvalue reference). Now I understand why you move the string to return it, though. Thanks.
James McNellis
GMan
So in conclusion, I think the best way would be, for both C++03 and C++0x, `std::string AddnewlinetoString(std::string s){ s += '\n'; return s; }` Considering the parameter: in C++03, when working with an lvalue you'd make a copy anyway, so nothing is lost. And a compiler will elision the copy of an rvalue. In C++0x, you again make the needed copy, but are guaranteed to move-construct the parameter when it's given an rvalue. So nothing lost there. And then you do your manipulation, then return the copy. In C++03, you get NRVO and in C++0x, you get an implicit move return. (Again, I think.)
GMan
@GMan: I had an email discussion with Doug Gregor on that matter, and he said that returning variables defined inside the function body are implicitly moved, but function arguments must be explicitly moved, because the function has no control over how they are created or some technical reason similar to that.
FredOverflow
@Fred: Ah, ok. Good to know. :)
GMan
Oh, and +1 then since I agree with your C++ variations, with that knowledge.
GMan
+3  A: 

Here is a corrected version (community wiki incase I missed anything)

// caller must free() returned buffer string!
char* AddnewlinetoString(char *s)
{
  size_t len;
  char * buffer;

  if (s == NULL)
    s = "";

  len = strlen(s);
  buffer = malloc(len+2);
  if (buffer == NULL)
    abort();
  strcpy(buffer,s);
  buffer[len] = '\n';
  buffer[len+1] = 0;
  return buffer;
}

As tony mentions, s may be a valid address but still be a malformed c-string, with no null bytes. The function could end up reading until it causes a segfault, or some other horrible thing. While this is still idiomatic C, most folks prefer counted strings (rather than null terminated ones.)

// caller must free() returned buffer string!
char* AddnewlinetoStringN(char *s, size_t len)
{
  char * buffer;

  if (s == NULL)
    s = "";

  buffer = malloc(len+1); // only add 1 byte, since there's no need for the nul
  if (buffer == NULL)
    abort();
  strncpy(buffer,s,len);
  buffer[len] = '\n';
  return buffer;
}
TokenMacGuy
You're one character short on your malloc I think - you need space for the \n and the terminating NULL, so len+2.
JosephH
I think you're right too, fixed now... (my C is a bit rusty)
TokenMacGuy
i'd personally check for s being null as the first thing I did. This is a common problem with string args and if you dont check for it, it will assert.
Tony Lambert
@Tony: Not a bad call, how do you like this version?
TokenMacGuy
there is just one more little hole.... if you pass in a string without a terminating zero, but to nail that down you would need to assume maximum lengths for strings.
Tony Lambert
Is this the level of question you need to pass to work for Microsoft these days? To work for Google they ask you how to move a mountain.
Tony Lambert
* This seems to be the best answer by far yet has one of the lowest scores!
Tony Lambert
+6  A: 

I would also add that the name of the method should stick to pattern and each word should start with capital letter:

char* AddNewlineToString(char *s)
{
}

ps. Thanks Konrad, I have changed the method name as you have suggested

marmich
But “newline” (as in “newline character”, not “new line of text”) is **one** word. The name should be `AddNewlineToString`.
Konrad Rudolph
A: 

In C++ it should be

std::string AddNewlineToString(const std::string& s) // pass by const reference
{
    return s + '\n'; // and let optimizer optimize memory allocations
}
Abyx
A: 

For c-style string it may be

char* // we want return a mutable string? OK
AddNewlineToString(
  const char* s // we don't need to change original string, so it's const
)
{
     const size_t MAX_SIZE = 1024; // if it's mutable string, 
                                   // it should have known capacity

     size_t len = strlen(s);            
     if(len + sizeof("\n") // to avoid the magic number "2"
         > MAX_SIZE)
         return NULL; // we don't know what to do with this situation,
                      // user will check result and make a decision -
                      // to log, raise exception, exit(), etc

     // static                    // we want thread-safe result
     char* buf = new char[1024];  // so we allocate memory in heap
         // and it's C-language-string but not C language :)

     memcpy(buf, s, len); // copy terminating zero, and rewrite it later? NO.
     memcpy(buf + len, "\n", sizeof("\n")); // compiler will do it in one action like
        // *(int16_t*)(buf + len) = *(int16_t*)"\n";
        // rather then two assigns

     return buf;
}
Abyx
Your use of `sizeof("\n")` was initially unclear; I'm not sure avoiding a "magic number" is worth it in this case.
Dennis Zickefoose
What's the point of using `new` but not `std::string`?
GMan
@Gman: for example if we want provide a C-compatible interface. With heap allocated memory, we should provide only one function to delete any heap-allocated memory, but for allocated-by-string memory we can't easily do it.
Abyx
@Abyx: Pretty strange to justify something with "It might be used in C." I wouldn't assume that in any of my code, and such an assumption is certainly outside the scope of the question.
GMan
@GMan: not "in C" but "C-compatible". It means "in C#, F#, Python (ctypes), Haskell and lots of other languages". You can't export function with std::string result from DLL, and use this function in module written in another language (or module compiled by another C++ compiler too). It's Microsoft's question, it's about Windows, not cross-platform programming with gcc only.
Abyx
@Abyx: Fair, but I reiterate my point: In C++, write C++. If you happen to need to interface with another language *then* change the code to do so. I think justifying your answers poor resource management with "It could be used by another language" is poor, since nothing says anything about such a requirement.
GMan
A: 

I would leave the Job Interview ....

Whether you like it or not, legacy code exists. This doesn't seem like an inappropriate way to verify a candidate is capable of working with such code. I've certainly seen worse examples.
Dennis Zickefoose
Exactly why would you leave the interview? This seems like exactly the kind of thing that should be in an interview, rather than all those stupid "how many gas stations does a city need" puzzle questions.
Kristopher Johnson
First off all the job interview was at Microsoft, but that's just me bashing on Microsoft. The real problem I have with this question is that the company apparently thinks the degree and the experience I got seems worthless to them. A correct way to handle a candidate is to ask for any code references. Its 2010 and most of us have some source code on the internet (oh wait .. I forgot MS hates open source). To check whether the candidate can program you should check that source first before starting questions about some niggy-naggy small meaningless code-pieces during the interview.
Being able to think is quite important in programming jobs. If you have some code snippets out there or even if you contributed to open source, if you can't think well, it may hurt the code base. There is usually no need to get the solution 100% correct the first time, because not always do you get 100% correct code the first time esp if you write it in C. The interviewers probably want to see your thinking process.
動靜能量
+1  A: 

No need for return pointer. Change the incoming pointer.

int len = strlen(s); s[len] = '\n'; s[len + 1] = '\0';

Manoj R
It will not work, first assignment replaces terminating zero, and second strlen will return invalid result. You should swap them.
Abyx
Nice. Edited now.
Manoj R
That need not work always. What if the input is a `string literal` or a char array which has `len-1` char already stuffed in so that when you access index `len+1` you are stepping past the allocated memory.
codaddict
+2  A: 

The main problem with this code is that it's vulnerable to a stack buffer overflow exploit. It's a classic example.

Basically, the input char* can be made longer than 1024 bytes; these extra bytes will then overwrite the stack, allowing an attacker to modify the function return pointer to point to their malicious code. Your program will then unwittingly execute the malicious code.

Microsoft might be expected to care a good deal about these kinds of exploits, since the Code Red Worm famously used a stack buffer overflow to attack hundreds of thousands of computers running IIS web server software in 2001.

Miguel Vieira
A: 

Can be made very simple using strdup:

char* AddnewlinetoString(char *s) {
char *buffer = strdup(s);
buffer[strlen(s)-1] = '\n';
return buffer;
}

A: 

Here goes a simpler way:

char* AddnewlinetoString(char *s)
{
  char buffer[strlen(s)+1];
  snprintf(buffer,strlen(s),"%s\n",s);
  return buffer;
}
gameover