Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to fix strcpy so that it detects overlapping strings

Tags:

c

overlap

strcpy

In an interview, I was asked to write an implementation of strcpy and then fix it so that it properly handles overlapping strings. My implementation is below and it is very naive. How do I fix it so that:

  1. It detects overlapping strings and
  2. after detecting, how do we deal with the overlap and proceed?

char* my_strcpy(char *a, char *b) {

     if (a == NULL || b == NULL) {
         return NULL;
     }
     if (a > b) {
         //we have an overlap?
         return NULL;
     }
     char *n = a;

     while (*b != '\0') {
         *a = *b;
         a++;
         b++;
     }
     *a = '\0';
     return n;
}

int main(int argc, char *argv[])
{
    char str1[] = "wazzupdude";
    char *after_cpy = my_strcpy(str1 + 2, str1);
    return 0;
}

EDIT:

So one possible implementation based on @Secure's answer is:

char* my_strcpy(char *a, char *b) {

    if (a == NULL || b == NULL) {
        return NULL;
    }

    memmove(a, b, strlen(b) + 1);
    return a;
}

If we don't rely on memmove, then

char* my_strcpy(char *a, char *b) {

    if (a == NULL || b == NULL) {
        return NULL;
    }

    if (a == b) {
        return a;
    }

    // case1: b is placed further in the memory
    if ( a <= b && a + strlen(a) > b ) {
        char *n = a;

        while(*b != '\0') {
            *a = *b;
            a++; b++;
        }
        *a = '\0';
        return n;
    }

    // case 2: a is further in memory
    else if ( b <= a && b + strlen(b) > a ) { 
        char *src = b + strlen(b) - 1; // src points to end of b
        char *dest = a;

        while(src != b) {
            *dest = *src;
            dest--; src--;  // not sure about this..
        }
        *a = '\0';
        return a;
    }
}
like image 341
user7 Avatar asked Sep 15 '11 08:09

user7


3 Answers

There is no portable way to detect this. You have to do pointer comparisons, and these are only defined within the same object. I.e. if the two strings do not overlap and are in fact different objects, then the pointer comparisons give you undefined behaviour.

I would let the standard library handle this, by using memmove(a, b, strlen(b) + 1).

EDIT:

As Steve Jessop pointed out in the comments, there actually is a portable but slow way to detect overlap in this case. Compare each address within b with the first and last address of a for equality. The equality comparison with == is always well defined.

So you have something like this:

l = strlen(b);
isoverlap = 0;
for (i = 0; i <= l; i++)
{
    if ((b + i == a) || (b + i == a + l))        
    {
        isoverlap = 1;
        break;
    }
}

EDIT 2: Visualization of case 2

You have something like the following array and pointers:

S t r i n g 0 _ _ _ _ _ _ _
^       ^
|       |
b       a

Note that b + strlen(b) results in a pointer to the terminating \0. Start one behind, else you need extra handling of edge cases. It is valid to set the pointers there, you just can't dereference them.

src = b + strlen(b) + 1;
dst = a + strlen(b) + 1;

S t r i n g 0 _ _ _ _ _ _ _
^       ^     ^       ^  
|       |     |       |
b       a     src     dst

Now the copy loop which copies the \0, too.

while (src > b)
{
    src--; dst--;
    *dst = *src;
}

The first step gives this:

src--; dst--;

S t r i n g 0 _ _ _ _ _ _ _
^       ^   ^       ^  
|       |   |       |
b       a   src     dst

*dst = *src;

S t r i n g 0 _ _ _ 0 _ _ _
^       ^   ^       ^  
|       |   |       |
b       a   src     dst

And so on, until src ends up equal to b:

S t r i S t r i n g 0 _ _ _
^       ^              
|       |            
b       a          
src     dst

If you want it a bit more hackish, you could compress it further, but I don't recommend this:

while (src > b)
    *(--dst) = *(--src);
like image 165
Secure Avatar answered Nov 11 '22 06:11

Secure


You could probably use memmove() if you expect the strings to be overlapping.

char* my_strcpy(char *a, char *b)
{
    memmove(a, b, strlen(b) + 1);
    return a;
}
like image 29
brain Avatar answered Nov 11 '22 05:11

brain


Note: Here, b is the address of the source string and a is the address of the destination.

With a > b you wouldn't necessarily have an overlap. If

(a <= b && a+strlen(a) >= b) || (b <= a && b+strlen(b) >= a)

then you have an overlap.

However, besides detecting overlap for the sake of interview, a > b should do fine for strcpy. The idea is this:

If b is placed further in the memory (b > a), then you can normally copy b into a. Parts of b will be overwritten, but you are already past that part.

If a is placed further in the memory (a > b), it means that possibly by writing on the first location of a, you have already overwritten a location in b with a higher index. In such a case, you should copy in the opposite direction. So instead of copy from index 0 to strlen(b)-1, you should copy from strlen(b)-1 to 0.

If you are confused how that helps, draw two overlapping arrays on paper and try to copy once from the beginning of the array and once from the end. Try this with the overlapping arrays both in cases a > b and a < b.

Note, if a == b, you don't need to actually copy anything and you can just return.

Edit: I am not sure, but reading the other solutions, it seems like this answer may not be completely portable. Beware of that.

like image 4
Shahbaz Avatar answered Nov 11 '22 06:11

Shahbaz