Страницы

Поиск по вопросам

пятница, 31 января 2020 г.

Адрес начала массива

#c #массивы #указатели


Есть массив данных, адрес начала этого массива, необходимо записать в память. С первого
взгляда, задача тривиальная, но на практике оказалось гораздо сложнее...

BYTE ST_1[] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66 };

VOID WINAPI WriteAddr()
{
     BYTE *rData = nullptr;

     rData = (BYTE*)malloc(0x90);

     if(rData)
     {
          ZeroMemory(rData, 0x90);

          memcpy(rData, &ST_1[0], sizeof(BYTE*));

          free(rData);
     }
}


Почему-то программа, записывает первые 4 байта массива, но, никак не адрес начала
массива в памяти. Получилось, тоже самое, если бы я написал

memcpy(rData, ST_1, sizeof(BYTE*));


Чуть-чуть поигрался и у меня получилось, то, чего я хотел, но, чего-то я не понимаю

BYTE ST_1[] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66 };

VOID WINAPI WriteAddr()
{
     BYTE *rData = nullptr, *pM = nullptr;

     rData = (BYTE*)malloc(0x90);

     if(rData)
     {
          ZeroMemory(rData, 0x90);

          pM = &ST_1[0];

          memcpy(rData, &pM, sizeof(BYTE*));

          free(rData);
     }
}


Причем здесь указатель на pM? В переменную pM мы поместили, только адрес начала массива,
но сама переменная находиться, совершенно по другому адресу...
    


Ответы

Ответ 1



Функция memcpy, объявленная как void *memcpy(void * restrict s1, const void * restrict s2, size_t n); копирует n символов, адресуемых указателем s2, в область памяти, адресуемой указателем s1. Дело в том, что массив - это именованная область памяти. Поэтому выражения &ST_1[0], ST_1 и &ST_1 имеют одно и то же значение, которое является адресом первого элемента массива и, соответственно, адресом этой именованной области. Поэтому если эти выражения использовать в memcpy, как, например, memcpy(rData, &ST_1[0], sizeof(BYTE*)); memcpy(rData, ST_1, sizeof(BYTE*)); memcpy(rData, &ST_1, sizeof(BYTE*)); то так как значения этих выражений равны, то это значение будет рассматриваться функцией как адрес области, откуда надо скопировать данные. Поэтому всегда будут копироваться элементы массива, расположенные в этой области. Вам сначала надо занести адрес массива в какую-то промежуточную переменную (область памяти, где будет храниться этот адрес, и откуда вы будете его копировать), а затем значение из этой переменной (области памяти) с помощью указателя на эту переменную (область памяти) копировать, используя memcpy, что вы и сделали во втором фрагменте кода pM = &ST_1[0]; memcpy(rData, &pM, sizeof(BYTE*));

Ответ 2



memcpy читает байты из указанного вами адреса. Вы указали адрес начала массива - оттуда и прочитано N байт. Чтобы memcpy прочитала нужный вам адрес начала его нужно куда-то поместить. А вообще void* ptr = &ST_1;

Ответ 3



sizeof(BYTE*) Это размер указателя - 4 или 8 байт. Иногда 2. Надо передавать размер массива самому, sizeof работает только для массивов со статическим размером. Понял. Надо было адрес копировать, а не первый элемент. Тогда так: BYTE *temp = ST_1; memcpy(rData, &temp, sizeof(temp));

Ответ 4



memcpy(rData, &ST_1[0], sizeof(BYTE*)); Не работает как надо, потому что ST_1[0] это содержимое первого элемента массива, &ST_1[0] — это указатель на первый элемент массива, ровно как и &ST_1. memcpy требует на вход указатель на данные, которые будут скопированы. В данном случае Вы подаете указатель на данные массива, поэтому копируется массив (4 байта). pM = &ST_1[0]; memcpy(rData, &pM, sizeof(BYTE*)); Работает как надо, поскольку pm содержит указатель на первый элемент массива а не содержимое элемента. В этом же случае memcpy получает на вход указатель на указатель (&pm) и в итоге копирует содержимое указателя pm. А вообще не понятно почему бы просто не сделать *rData = ST_1?

Ответ 5



sizeof(BYTE*) дает размер указателя (byte*). На 32 битных системах он равен 4 байтам. memcpy потому и копирует 4 байта, потому что больше не просили. Правильно будет вот так: memcpy(куда, откуда,sizeof(byte*)*(число элементов массива)) Сам же адрес начала массива в памяти - &ST_1[0], отсюда он и начинается

Ответ 6



Вам нужно разместить в указанной памяти какой-то адрес? Нет ничего проще (конечно, если в указанной памяти выделено достаточно для sizeof(void *) (это размер указателя) места) и memcpy() абсолютно не нужна для такой задачи. static inline char *put_addr (char *dst, void *addr) { if (dst) *(void **)dst = addr; return dst; } Просто играемся приведением типов и пишем наш адрес в нужное место простым присваиванием (обратите внимание, на самом деле (при оптимизирующей компиляции) тут нет никаких вызовов функций). Пример использования (в чем-то похож на Ваш (даже с malloc того же размера, что и исходный массив, адрес которого надо занести в память)) #include #include #include #include #include int main (int ac, char *av[]) { char a[] = "abcdefghijklmn"; char *x = (typeof(x))put_addr((char *)malloc(sizeof(a)), a); if (!x) err(EX_OSERR, "put_addr(malloc(), ...)"); printf("source addr: %p addr in malloc: %p\n", a, *(void **)x); memcpy(x, a, sizeof(a)); put_addr(x, a); printf("again with source copy: %p %p [%s]\n", a, *(void **)x, x + sizeof(void *)); return puts("End") == EOF; } Транслируем и запускаем (32-bit linux) avp@avp-xub11:hashcode$ g++ c1.c && ./a.out source addr: 0xbfa84e0d addr in malloc: 0xbfa84e0d again with source copy: 0xbfa84e0d 0xbfa84e0d [efghijklmn] End avp@avp-xub11:hashcode$ g++ -S -O c1.c && grep put_data c1.s avp@avp-xub11:hashcode$ Как видите, оптимизатор действительно выбрасывает вызов put_data(). Если что-то непонятно, спрашивайте. Пожалуй, надо добавить несколько слов о выравнивании данных в памяти (alignment, здесь подробнее (но по английски)). Приведенный выше код put_addr() будет без проблем работать на x-86 (наиболее распространен), а вот на большинстве других архитектур (ARM (большинство планшетов и смартфонов), Sparc, Power ...) будет "падать" с segmentation fault в случае, если переданный адрес dst не выровнян на естественную для указателя границу (4 для 32-bit, 8 для 64-bit машин). Более переносимый вариант на gcc/g++ может выглядеть так static char *put_addr (char *dst, void *addr) { if (dst) #ifdef NEED_ALIGNMENT #ifdef USE_MEMCPY memcpy(dst, &addr, sizeof(void *)); #else { struct { void *v __attribute__((packed)); } *s = (typeof(s))dst; s->v = addr; } #endif #else *(void **)dst = addr; #endif return dst; } Определение переменных времени компиляции NEED_ALIGNMENT и USE_MEMCPY как обычно ложится на плечи программиста (ну, вычисление NEED_ALIGNMENT можно и автоматизировать, базируясь на выводе gcc -dM -E -

Ответ 7



К сожалению, вы не можете получить адрес переменной массива, созданного на стеке, потому что ее не существует в памяти. Вы правильно создали временную переменную. Может как-то так (чтобы не создавать временную переменную): template class rvalue_ptr_base { public: operator T*() const{ return ptr; } protected: T* ptr; }; template class rvalue_ptr : public rvalue_ptr_base{ public: rvalue_ptr(T&& t) : data(std::move(t)) { ptr = &data; } protected: T data; }; template class rvalue_ptr : public rvalue_ptr_base{ public: rvalue_ptr(T* t) { data = t; ptr = &data; } protected: T* data; }; // ... memcpy(rData, rvalue_ptr(ST_1), sizeof(BYTE*));

Ответ 8



Из заголовка функции void *memcpy(void *dst, const void *src, size_t n) видно, что вторым агрументом является адрес, а в описании функции memcpy сказано: Функция копирует n байт из области памяти, на которую указывает src, в область памяти, на которую указывает dst. Функция возвращает адрес назначения dst. Для того, чтобы адрес массива был записан в указанную область памяти, адрес массива вначале нужно поместить к примеру в переменную, а потом адрес этой переменной передать в memcpy вторым аргументом. BYTE ST_1[] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66 }; VOID WINAPI WriteAddr() { BYTE *rData = nullptr; BYTE* pM = nullptr; rData = (BYTE*)malloc(0x90); if(rData) { ZeroMemory(rData, 0x90); pM = ST_1; memcpy(rData, &pM, sizeof(BYTE)); free(rData); } }

Ответ 9



Приблизительно вот так: memcpy(rData, &ST_1, sizeof(BYTE*));

Комментариев нет:

Отправить комментарий