Почему memcpy не выполняет копирование в элемент локального массива простого объекта?

StackOverflow https://stackoverflow.com/questions/1570330

  •  21-09-2019
  •  | 
  •  

Вопрос

Классический memcpy gotcha с массивами C в качестве аргументов функции.Как указано ниже, у меня ошибка в моем коде, но ошибочный код сработал в локальном контексте!

Я только что столкнулся с этим странным поведением в задании на перенос, где я эмулирую воспроизведение кода операции Macintosh Picture с использованием objects.Мой объект DrawString рисовал мусор при воспроизведении, потому что ему, по-видимому, не удалось скопировать строковый аргумент.Ниже приведен тестовый пример, который я написал - обратите внимание, как работает цикл ручного копирования, но memcpy завершается с ошибкой.Трассировка в отладчике Visual Studio показывает, что memcpy ovewrites адресата с мусором.

Memcpy для двух локальных массивов Str255 работает нормально.

Когда один из них является членом объекта в стеке, происходит сбой (в другом тестировании это также приводит к сбою, когда объект находится в куче).

Следующий пример кода показывает, что memcpy вызывается в operator= .Я переместил его туда после сбоя в конструкторе, но разницы не было.

typedef unsigned char Str255[257];

// snippet that works fine with two local vars
Str255 Blah("\004Blah");
Str255 dest;
memcpy(&dest, &Blah, sizeof(Str255));  // THIS WORKS - WHY HERE AND NOT IN THE OBJECT?

/*!
class to help test  CanCopyStr255AsMember
*/
class HasMemberStr255  {
public:
    HasMemberStr255()
    {
        mStr255[0] = 0;
    }

    HasMemberStr255(const Str255 s)
    {
        for (int i = 0; i<257; ++i)
        {
            mStr255[i] = s[i];
            if (s[i]==0)
                return;
        }
    }

    /// fails
    void operator=(const Str255 s)  {
        memcpy(&mStr255, &s, sizeof(Str255));
    };
    operator const Str255&() { return mStr255; }

private:
    Str255 mStr255;
};
-

/*!
Test trivial copying technique to duplicate a string
Added this variant using an object because of an apparent Visual C++ bug.
*/
void TestMacTypes::CanCopyStr255AsMember()
{
    Str255 initBlah("\004Blah");
    HasMemberStr255 blahObj(initBlah);
// using the operator= which does a memcpy fails   blahObj = initBlah;

    const Str255& dest = blahObj;  // invoke cast operator to get private back out
    CPPUNIT_ASSERT( dest[0]=='\004' );
    CPPUNIT_ASSERT( dest[1]=='B' );
    CPPUNIT_ASSERT( dest[2]=='l' );
    CPPUNIT_ASSERT( dest[3]=='a' );
    CPPUNIT_ASSERT( dest[4]=='h' );
    CPPUNIT_ASSERT( dest[5]=='\0' );  //  trailing null
}
Это было полезно?

Решение

Вероятно, это хороший пример того, почему (на мой взгляд) это плохая идея typedef типы массивов.

В отличие от других контекстов, в объявлениях функций параметр типа array всегда настраивается на эквивалентный тип указателя.Когда массив передается функции, он всегда распадается на указатель на первый элемент.

Эти два фрагмента эквивалентны:

typedef unsigned char Str[257];
Str src = "blah";
Str dst;
memcpy( &dst, &src, sizeof(Str) ); // unconventional

unsigned char src[257] = "blah";
unsigned char dst[257];
memcpy(&dst, &src, sizeof(unsigned char[257])); // unconventional

В этом последнем случае &dst и &src оба относятся к типу unsigned char (*)[257] но значение этих указателей совпадает со значением указателей на первый элемент каждого массива, что dst и src распался бы на, если бы был передан непосредственно в memcpy вот так.

memcpy(dst, src, sizeof(unsigned char[257])); // more usual

memcpy принимает void* аргументы, поэтому типы исходных указателей не имеют значения, только их значения.

Из-за правила для объявлений параметров (тип массива любого или неуказанного размера подгоняется к эквивалентному типу указателя), эти объявления для fn все они эквивалентны:

typedef unsigned char Str[257];
void fn( Str dst, Str src );

void fn( unsigned char dst[257], unsigned char src[257] );

void fn( unsigned char dst[], unsigned char src[] );

void fn( unsigned char* dst, unsigned char* src );

Глядя на этот код, становится более очевидным, что значения, передаваемые в memcpy в этом случае это указатели на переданные указатели, а не указатели на фактические unsigned char массивы.

// Incorrect
void fn( unsigned char* dst, unsigned char* src )
{
    memcpy(&dst, &src, sizeof(unsigned char[257]));
}

С typedef ошибка не столь очевидна, но все еще присутствует.

// Still incorrect
typedef unsigned char Str[257];
void fn( Str dst, Str src )
{
    memcpy(&dst, &src, sizeof(Str));
}

Другие советы

Вы должны написать memcpy(mStr255, s, sizeof(Str255));.Без '&'. Str255 это уже указатель.Это соответствует стандарту C ++ 4.2:

Значение lvalue или rvalue типа “массив из N T“ или "массив с неизвестной границей T" может быть преобразовано в значение типа “указатель на T.” Результатом является указатель на первый элемент массива.

Почему это где-то работает?Есть два разных указателя (для mStr255 и &mStr255) и у них есть разные типы — unsigned char * и unsigned char (*)[257].Адрес массива совпадает с адресом первого элемента в массиве, но когда вы передаете его в качестве аргумента функции, вы получите адрес переменной в стеке.С помощью набора текста Str255 вы скрываете разницу.Проверьте следующий образец:

unsigned char Blah[10] = "\004Blah";

struct X
{
    void f1( unsigned char(&a)[10] ) // first case (1)
    {
      void* x1 = &a; // pointer to array of unsigned char
      void* x2 = a;  // pointer to unsigned char due to implicit conversion array-to-pointer
    }
    void f2( unsigned char* a )     // second case (2)
    {
      void* x1 = &a; // pointer to variable 'a' which is on the stack
      void* x2 = a;  // pointer to unsigned char
    }
    unsigned char x[10];
};

int main( int argc, char ** argv )
{
    X m;
    m.f1( Blah ); // pass by reference
    m.f2( Blah ); // implicit array-to-pointer conversion

    return 0;
}

Когда вы будете писать void f( Str255 a ), это равно второму случаю.

Если я правильно читаю (а мой C ++ немного подзабылся), ваш класс на самом деле никогда не выделяет пространство для переменной mStr.Вы объявляете его (но, похоже, не выделяете его) в разделе private и инициализируете первый элемент значением 0 в конструкторе, но, похоже, не каждый из вас на самом деле создает объект Str255.

Возможно, вам потребуется заменить личное объявление на Str255 mStr(), или вам может понадобиться сделать что-то в конструкторе, например mStr = new Str255()

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top