как исправить strcpy, чтобы он обнаружил перекрывающиеся строки

в интервью Меня попросили написать реализацию strcpy, а затем исправить ее так, чтобы она правильно обрабатывала перекрывающиеся строки. Моя реализация ниже, и она очень наивна..как исправить это, чтобы a) он обнаружил перекрывающиеся строки и b) после обнаружения..как мы справимся с дублированием и продолжим?

    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 != ''){
            *a = *b;
            a++;
            b++;
        }
        *a = '';
        return n;
    }

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

EDIT:

таким образом, одна возможная реализация на основе ответа @Secure:

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

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

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

Если мы не полагаемся на memmove, то

 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 != ''){
                    *a = *b;
                    a++; b++;
                  }
                  *a = '';
                 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 = '';
                 return a;
              }

8 ответов


нет портативного способа обнаружить это. Вы должны выполнять сравнения указателей, и они определяются только в одном объекте. Т. е. если две строки не перекрываются и на самом деле являются разными объектами, то сравнение указателей дает вам неопределенное поведение.

я бы позволил стандартной библиотеке справиться с этим, используя memmove(a, b, strlen(b) + 1).

EDIT:

как отметил Стив Джессоп в комментариях, на самом деле есть портативный, но медленно способ обнаружения перекрытия в этом случае. Сравните каждый адрес в пределах b с первым и последним адресом a для равенства. Сравнение равенства с == всегда четко определены.

так что у вас есть что-то вроде этого:

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

EDIT 2: визуализация случая 2

у вас есть что-то вроде следующего массива и указатели:

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

отметим, что b + strlen(b) приводит к указателю на завершение \0. Начать одно за, иначе вам нужна дополнительная обработка крайних случаев. Действительно, чтобы установить указатели там, вы просто не можете разыменовать их.

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

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

теперь цикл копирования, который копирует \0, тоже.

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

первый шаг дает это:

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

и так далее, пока src заканчивается равен b:

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

если вы хотите, чтобы он немного более хакерский, вы можете сжать его дальше, но я не рекомендую это:

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

Примечание: Здесь b - адрес исходной строки и a - адрес места назначения.

С a > b у вас не обязательно будет перекрытие. Если

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

тогда у вас есть перекрытие.

Однако, помимо обнаружения перекрытия ради интервью,a > b должно быть хорошо для strcpy. Идея такова:

если b помещается дальше в память (b > a), то вы можете нормально скопировать b в a. Части b будет перезаписано, но вы уже прошли эту часть.

если a помещается дальше в память (a > b), это означает, что возможно написав на первом месте в a, вы уже перезаписали местоположение в b С более высоким индексом. В таком случае следует копировать в противоположном направлении. Поэтому вместо копирования из index 0 to strlen(b)-1, вы должны скопировать из strlen(b)-1 для 0.

если вы путаете, как это помогает, нарисуйте два перекрывающихся массива на бумаге и попробуйте скопировать один раз с начала массива и один раз с конца. Попробуйте это с перекрывающимися массивами как в cases a > b и a < b.

обратите внимание, если a == b, вам не надо ничего копировать и вы можете просто вернуться.

Edit: я не уверен, но, читая другие решения, кажется, что этот ответ может быть не полностью переносимым. Остерегаться этого.


вы, вероятно, можете использовать memmove (), если ожидаете, что строки будут перекрываться.

char* my_strcpy(char *a, char *b)
{
    memmove(a, b, strlen(b) + 1);
    return a;
}

if a > b; then
    copy a from the beginning
else if a < b; then
    copy a from the ending
else // a == b
    do nothing

Вы можете обратиться к реализация of memmove, это похоже на то, что я сказал.


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

(*) вы должны кэшировать strlen (b) для повышения производительности

что он делает:
проверяет, является ли a+len [адрес + дополнительных LEN байтов] находится внутри строки или a [адрес a] находится внутри строки, это единственные возможности для перекрытия строки.


если эти две строки перекрываются, то при копировании вы будете запускать оригинал a или b указатели.

предполагая, что strcpy (a, b ) грубо означает a b ' s позиция.

вам нужно только сохранить b исходное положение, и при копировании, проверьте, что вы не достигли его. Кроме того, не записывайте трейлинг-ноль, если вы достигли эта позиция.

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

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

    char *n = a;
    const char * oldB = b;

    while( *b != ''
       &&  a != oldB )
    {
        *a = *b;
        a++;
        b++;
    }

    if ( a != oldB ) {
        *a = '';
    }

    return n;
 }

этот алгоритм просто перестает копировать. Возможно, вы хотите сделать что-то еще, например, пометить условие ошибки или добавить метку конца строки в предыдущую позицию (хотя сбой молча (как это делает алгоритм на данный момент) не является лучшим вариантом).

надеюсь, что это помогает.


меня спросили об этом в недавнем интервью. Нам не нужно "обнаруживать" перекрытия. Мы можем написать strcpy таким образом, чтобы позаботиться о перекрывающихся адресах. Ключ должен копировать из конца исходной строки, а не из начала.

вот быстрый код.

void str_copy(const char *src, char *dst) 
{
    /* error checks */

    int i = strlen(a); /* may have to account for null character */

    while(i >= 0) 
    {
        dst[i] = src[i];  
        i--; 
    }
}

EDIT: это работает только тогда, когда a b скопируйте с самого начала.


даже без использования сравнений реляционных указателей,memmove, или эквивалент, можно закодировать версию strcpy который будет выполняться как strlen и memcpy в непересекающихся случае, и как сверху вниз скопировать в дублирование случае. Ключ состоит в том, чтобы использовать тот факт, что если первый байт назначения считывается, а затем заменяется нулем, вызывая strlen на источнике и добавление к указателю источника возвращенного значения даст законный указатель что будет равно началу назначения в случае "хлопотного перекрытия". Если источник и назначение являются разными объектами, указатель "source plus strlen" может быть безопасно вычислен и замечен неравным назначению.

в случае, если добавление длины строки к исходному указателю приводит к указателю назначения, замена нулевого байта на значение более раннего чтения и вызов strlen в месте назначения позволит коду определить конечный адрес строки источника и назначения. Кроме того, длина исходной строки будет указано расстояние между указателями. Если это значение велико (возможно, больше 16 или около того), код может эффективно подразделить операцию "переместить" на нисходящую последовательность операций memcpy. В противном случае строка может быть скопирована с помощью нисходящего цикла однобайтовых операций копирования или с последовательностью операций"memcpy-источник-буфер"/" memcpy-буфер-назначение " [если стоимость одного байта memcpy меньше половины цикла копирования отдельных символов, использование ~ 256-байтового буфера может быть полезной оптимизацией].