Доступ к полю неизвестной структуры
От: TailWind  
Дата: 17.04.10 14:32
Оценка:
Посоветуйте, как улучшить / ускорить / сделать нагляднее

Задача: знаем смещение поля в структуре, нужно организовать быстрый и удобный доступ

Структура:
struct Record
{
  ...
  ULONG Field;
  ...
};


Доступ через класс:

class Ptr
{
  public:
    Ptr(ULONG p) { Offset = p; }
    //
    ULONG& Get(UCHAR *Staff) { return *(ULONG*)(Staff + Offset); }
  private:
    ULONG Offset;
};


Применение:

Record Records[10000]; 
//
UCHAR *d  = (UCHAR*)Records;
Ptr    p  = Ptr(offsetof(Record, Field));
ULONG  s  = sizeof(Record);
//
Proc(d, p, s);


Процедура:

void Proc(UCHAR *d, Ptr p, ULONG Size)
{
  for (ULONG i=0; i<10000; i++)
  {
    ULONG &f = P.Get(d);
    //
    f = 123;
    //
    p += Size;
  }
}
Re: Доступ к полю неизвестной структуры
От: Kirikaza Россия kirikaza.ru
Дата: 17.04.10 17:57
Оценка:
Здравствуйте, TailWind, Вы писали:

TW>
TW>void Proc(UCHAR *d, Ptr p, ULONG Size) {
TW>  for (ULONG i=0; i<10000; i++) {
TW>    ULONG &f = P.Get(d);
TW>    f = 123;
TW>    p += Size;
TW>  }
TW>}
TW>

А как это можно прибавлять что-то там к p, если это экземпляр класса без перегруженного оператора? Если же это должен быть указатель, тогда мне непонятно, зачем всё это.

С одной стороны, если вы уверены, что записи будут храниться сплошняком в памяти, а не в виде списка или чего-то ещё, то можно и в лоб сделать, безо всяких доп. классов:

    Record records[REC_NUM]; 
    proc2(records);

...

void proc2(Record* records) {
    void* ptr = static_cast<void*>(records);
    ptr = static_cast<char*>(ptr) + offsetof(Record, field);
    for (i = 0; i < REC_NUM; ++i) {
        long* field_ptr = (long*) rec_ptr;
        cout << *field_ptr << endl;
        ++rec_ptr;
    }
}


Если же НЕ сплошняком или просто хочется свою обёртку сделать, тогда вот так как-то можно:
class RecordWrapper : public Record {
public:
    long* field() {
        void* ptr = static_cast<void*>(this);
        ptr = static_cast<char*>(ptr) + offsetof(Record, field);
        return static_cast<long*>(ptr);
    }
};

void proc3(Record* records) {
    RecordWrapper* wrappers = static_cast<RecordWrapper*>(records);
    for (int i = 0; i < REC_NUM; ++i) {
        cout << *wrappers[i].field() << endl;
    }
}

Тут только важно учитывать, что в RecordWrapper нельзя новые поля добавлять...

Но смысла осбого я в этом не вижу... разве что для экспериментов.
Re[2]: Доступ к полю неизвестной структуры
От: TailWind  
Дата: 17.04.10 18:34
Оценка:
TW>> p += Size;

Ой, извините
Конечно же d += Size;
Re[3]: Доступ к полю неизвестной структуры
От: Kirikaza Россия kirikaza.ru
Дата: 17.04.10 18:52
Оценка:
Здравствуйте, TailWind, Вы писали:

TW>Ой, Конечно же d += Size;


А, ну да Тогда ясно. Но вот обёртка Ptr -- не очень понятна. В общем, вместо того, чтобы вручную прибавлять sizeof(Record), можно указатель сделать типа Record* и затем делать инкремент этого указателя. Не могу сказать, насколько это очевидно, но зато этим займётся компилятор.
Re[4]: Доступ к полю неизвестной структуры
От: TailWind  
Дата: 17.04.10 19:10
Оценка:
K>можно указатель сделать типа Record* и затем делать инкремент этого указателя

Загвоздка как раз в том, что Record не известен в Proc, известен только её размер

Я праавильно понимаю что такой указатель смастерить не получиться?
Я кучу времени сегодня форум читал
Так и не нашёл такого способа

Что-то вроде этого хотелось:


struct ZZZ
{
  UCHAR Dummy[sizeof(Record)];
};

ULONG ZZZ::*p = offsetof(Record, Field); // но так нельзя :(

void Proc(...)
{
  ZZZ *z = (ZZZ*) d;
  //
  z->*p = 123;
  //
  z++;
}


Можно конечно вот так извратиться, но это UB имхо

struct ZZZ
{
  UCHAR Dummy[Offset];
  ULONG Field;
  UCHAR Dummy[sizeof(Record) - Offset - sizeof(Field)];
};
Re[5]: Доступ к полю неизвестной структуры
От: Kirikaza Россия kirikaza.ru
Дата: 17.04.10 19:58
Оценка:
Здравствуйте, TailWind, Вы писали:

TW>Загвоздка как раз в том, что Record не известен в Proc, известен только её размер


TW>Можно конечно вот так извратиться, но это UB имхо:


TW>
TW>struct ZZZ {
TW>  UCHAR Dummy[Offset];
TW>  ULONG Field;
TW>  UCHAR Dummy[RecordSize - Offset - sizeof(ULONG)];
TW>};
TW>

Да, при некруглом значении Offset из-за выравнивания может всё уплыть.

Можно и так:
union ZZZ {
  UCHAR Dummy[RecordSize];
  struct {
    UCHAR Dummy2[Offset];
    ULONG Field;
  } s;
};

Вроде выглядит поаккуратнее, но та же проблема с выравниванием остаётся. Даже размер структуры может стать больше. Скорее всего, нужно использовать pragmы, чтобы совпасть с теми параметрами, которые имеются в исходной структуре.

Видимо, будем ждать тех, кто с этим сталкивался на практике.
Re: Доступ к полю неизвестной структуры
От: rg45 СССР  
Дата: 18.04.10 07:45
Оценка:
Здравствуйте, TailWind, Вы писали:

TW>Посоветуйте, как улучшить / ускорить / сделать нагляднее

TW>Задача: знаем смещение поля в структуре, нужно организовать быстрый и удобный доступ

Как-то так:

//Сначала делаем универсальный итератор для навигации 
//по определенному полю в массиве однотипных структур

typedef unsigned char UCHAR;
typedef unsigned long ULONG;

template<typename FieldT> 
class FieldIterator
{
public:
  template<typename StructT>
  FieldIterator(StructT* sructures, FieldT StructT::*field)
  : _pointer_to_field(&(sructures->*field))
  , _increment_in_bytes(sizeof(StructT))
  { }
  
  FieldT& operator*() const { return *_pointer_to_field; }
  FieldIterator& operator++() { reinterpret_cast<UCHAR*&>(_pointer_to_field) += _increment_in_bytes; return *this; }
  FieldIterator& operator++(int) const { FieldIterator it(*this); ++*this; return it; }

private:
  FieldT* _pointer_to_field;
  ULONG _increment_in_bytes;
};

//И теперь применение этого итератора

#include <iostream>

void Proc(FieldIterator<ULONG> iterator, ULONG count)
{
  for(ULONG i = 0; i < count; ++i)
  {
    *iterator = 100 + i * 10;
    std::cout << *iterator << ", ";
    ++iterator;
  }
  std::cout << std::endl;  
}

struct Record
{
  int i;
  ULONG field;
  bool b;
};

int main()
{
  Record Records[1000]; 
  Proc(FieldIterator<ULONG>(Records, &Record::field), 1000);
}
--
Re[2]: Доступ к полю неизвестной структуры
От: Kirikaza Россия kirikaza.ru
Дата: 18.04.10 07:59
Оценка:
Здравствуйте, rg45, Вы писали:
reinterpret_cast<UCHAR*&>(_pointer_to_field) += _increment_in_bytes;

А насколько безопасно/порядочно/этично использовать char* для адресной арифметики с шагом в ровно один байт?
Re[3]: Доступ к полю неизвестной структуры
От: rg45 СССР  
Дата: 18.04.10 08:04
Оценка:
Здравствуйте, Kirikaza, Вы писали:

K>Здравствуйте, rg45, Вы писали:

K>
K>reinterpret_cast<UCHAR*&>(_pointer_to_field) += _increment_in_bytes;
K>

K>А насколько безопасно/порядочно/этично использовать char* для адресной арифметики с шагом в ровно один байт?

Гарантировано стандартом:

5.3.3/1
The sizeof operator yields the number of bytes in the object representation of its operand. The operand is either an expression, which is not evaluated, or a parenthesized type-id. The sizeof operator shall not be applied to an expression that has function or incomplete type, or to an enumeration type before all its enumerators have been declared, or to the parenthesized name of such types, or to an lvalue that designates a bit-field. sizeof(char), sizeof(signed char) and sizeof(unsigned char) are 1; the result of sizeof applied to any other fundamental type (3.9.1) is implementation-defined. [Note: in particular, sizeof(bool) and sizeof(wchar_t) are implementation-defined.69) ] [Note: See 1.7 for the definition of byte and 3.9 for the definition of object representation. ]

--
Re[4]: Доступ к полю неизвестной структуры
От: TailWind  
Дата: 18.04.10 11:51
Оценка:
А как сделать не так:

int main()
{
  Record Records[1000]; 
  Proc(FieldIterator<ULONG>(Records, &Record::field), 1000);
}


А так:

int main()
{
  FieldIterator<ULONG> Iter(&Record::field);
  Record Records[1000]; 
  Proc(Iter, 1000);
}
Re[5]: Доступ к полю неизвестной структуры
От: rg45 СССР  
Дата: 18.04.10 14:44
Оценка:
Здравствуйте, TailWind, Вы писали:

TW>...

TW>А так:

TW>
TW>int main()
TW>{
TW>  FieldIterator<ULONG> Iter(&Record::field);
TW>  Record Records[1000]; 
TW>  Proc(Iter, 1000);
TW>}
TW>


Непонятно для чего в этом случае массв Records вообще нужен. Посмотри, ты его создал, но никак не использовал — ни при конструировании итератора, ни в процедуру не передал
--
Re[6]: Доступ к полю неизвестной структуры
От: TailWind  
Дата: 18.04.10 15:03
Оценка:
R>Непонятно для чего в этом случае массв Records вообще нужен. Посмотри, ты его создал, но никак не использовал — ни при конструировании итератора, ни в процедуру не передал

Извините
Плохо формулирую, так ещё и ошибки леплю


int main()
{
  FieldIterator<ULONG> Iter(&Record::field);
  Record Records[1000]; 
  Proc(Records, Iter, 1000);
}

void Proc(UCHAR*, Iter I, ULONG Size) {...}
Re[4]: Доступ к полю неизвестной структуры
От: Masterkent  
Дата: 18.04.10 16:23
Оценка: +1
rg45:

K>>Здравствуйте, rg45, Вы писали:

K>>
K>>reinterpret_cast<UCHAR*&>(_pointer_to_field) += _increment_in_bytes;
K>>

K>>А насколько безопасно/порядочно/этично использовать char* для адресной арифметики с шагом в ровно один байт?

R>Гарантировано стандартом:

R>

R>5.3.3/1
R>The sizeof operator yields the number of bytes in the object representation of its operand. The operand is either an expression, which is not evaluated, or a parenthesized type-id. The sizeof operator shall not be applied to an expression that has function or incomplete type, or to an enumeration type before all its enumerators have been declared, or to the parenthesized name of such types, or to an lvalue that designates a bit-field. sizeof(char), sizeof(signed char) and sizeof(unsigned char) are 1; the result of sizeof applied to any other fundamental type (3.9.1) is implementation-defined. [Note: in particular, sizeof(bool) and sizeof(wchar_t) are implementation-defined.69) ] [Note: See 1.7 for the definition of byte and 3.9 for the definition of object representation. ]

Так-то оно так, только вот в данном случае мы через lvalue типа unsigned char* пытаемся оперировать указателем, который в действительности может не являться указателем на unsigned сhar. И тут следует обратить внимание на 3.10/15:

If a program attempts to access the stored value of an object through an lvalue of other than one of the following
types the behavior is undefined [Footnote 48: The intent of this list is to specify those circumstances in which an object may or may not be aliased.]
— the dynamic type of the object,
— a cv-qualified version of the dynamic type of the object,
— a type that is the signed or unsigned type corresponding to the dynamic type of the object,
— a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,
— an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union),
— a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,
— a char or unsigned char type.

Re[5]: Доступ к полю неизвестной структуры
От: Kirikaza Россия kirikaza.ru
Дата: 18.04.10 17:54
Оценка:
Здравствуйте, Masterkent, Вы цитировали стандарт:

If a program attempts to access the stored value of an object through an lvalue of other than one of the following
types the behavior is undefined [Footnote 48: The intent of this list is to specify those circumstances in which an object may or may not be aliased.]
— the dynamic type of the object,
— a cv-qualified version of the dynamic type of the object,
— a type that is the signed or unsigned type corresponding to the dynamic type of the object,
— a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,
— an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union),
— a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,
— a char or unsigned char type.

Т.е. из последней строки как бы следует, что указатель как раз и надо приводить через reinterpret_cast<char*>, чтобы добиться побайтовой адресной арифметики? Т.е. все реальные пацаны так и делают? Не приходилось мне драйвера писать, потому и спрашиваю.
Re[7]: Доступ к полю неизвестной структуры
От: rg45 СССР  
Дата: 18.04.10 19:45
Оценка:
Здравствуйте, TailWind, Вы писали:

TW>
TW>int main()
TW>{
TW>  FieldIterator<ULONG> Iter(&Record::field);
TW>  Record Records[1000]; 
TW>  Proc(Records, Iter, 1000);
TW>}

TW>void Proc(UCHAR*, Iter I, ULONG Size) {...}
TW>



Ох, не нравится мне эта заливная рыба. Тут явно требуется редизайн. Только рати того, чтоб не останавливаться на полпути доведу уже начатое до какой-то точки:
template<typename FieldT> 
class FieldIterator
{
public:
  FieldIterator(FieldT* pointer_to_field, int increment_in_bytes)
  : _pointer_to_field(pointer_to_field)
  , _increment_in_bytes(increment_in_bytes)
  { }

  FieldT& operator*() const { return *_pointer_to_field; }
  FieldIterator& operator++() 
  { 
    reinterpret_cast<char*&>(_pointer_to_field) += _increment_in_bytes; 
    return *this; 
  }

private:
  FieldT* _pointer_to_field;
  int _increment_in_bytes;
};

template<typename FieldT, typename StructT>
FieldIterator<FieldT> field_iterator(StructT* item, int offset)
{
  char* field_bytes = reinterpret_cast<char*>(item) + offset;
  FieldT* poiter_to_field = reinterpret_cast<FieldT*>(field_bytes);
  return FieldIterator<FieldT>(poiter_to_field, sizeof(StructT));
}

template<typename StructT, typename FieldT>
int field_offset(FieldT StructT::*field)
{
  return reinterpret_cast<int>(&(static_cast<StructT*>(0)->*field));
}

//Использование

#include <iostream>

typedef unsigned long ULONG;

struct Record
{
  int i;
  ULONG field;
  bool b;
};

void Proc(FieldIterator<ULONG> iterator, int count)
{
  for(int i = 0; i < count; ++i)
  {
    *iterator = i;
    std::cout << *iterator << ", ";
    ++iterator;
  }
  std::cout << std::endl;  
}

int main()
{
  int offset = field_offset(&Record::field);
  Record records[100]; 
  Proc(field_iterator<ULONG>(records, offset), 100);
}
--
Re[8]: Доступ к полю неизвестной структуры
От: TailWind  
Дата: 18.04.10 20:03
Оценка:
Погонял в реальной проге
Вроде ничего
Синтаксис удобный получился

Спасибо
Re[5]: Доступ к полю неизвестной структуры
От: rg45 СССР  
Дата: 18.04.10 20:17
Оценка:
Здравствуйте, Masterkent, Вы писали:

M>Так-то оно так, только вот в данном случае мы через lvalue типа unsigned char* пытаемся оперировать указателем, который в действительности может не являться указателем на unsigned сhar. И тут следует обратить внимание на 3.10/15:


Согласен, косяк. Можно обойти, если в итераторе инкапсулировать указатель типа unsigned char*, и реинтерпретировать в операторе разыменования, а не в инкременте:
template<typename FieldT> 
class FieldIterator
{
public:
  
  FieldT& operator*() const 
  { 
    return *reinterpret_cast<FieldT*>(_pointer_to_field); 
  }

  FieldIterator& operator++() 
  { 
     _pointer_to_field += _increment_in_bytes; 
      return *this; 
  }

private:
  char* _pointer_to_field;
  int _increment_in_bytes;
};
--
Re: Доступ к полю неизвестной структуры
От: sokel Россия  
Дата: 19.04.10 06:59
Оценка:
Мне кажется, проще завернуть обращение к члену в функтор и, если смещение определяется динамически, передавать этот функтор параметром, примерно так:

#include <iostream>

template<typename iterator, typename m_get>
void proc_members(iterator from, iterator to, m_get member)
{
    for(;from!=to; ++from) std::cout << member(*from) << " ";
    std::cout << std::endl;
}
// если член стурктуры известен на этапе компиляции
template<typename T, typename M, M T::*m_ptr>
struct member
{
    const M& operator()(const T& v) const { return v.*m_ptr; } 
    M& operator()(T& v) const { return v.*m_ptr; } 
};
// получение члена по смещению
template<typename T, typename M, size_t m_offset = 0>
struct member_by_offset
{
    const size_t offset;
    member_by_offset(size_t offset = m_offset) : offset(offset) {}
    const M& operator()(const T& v) const { return *(const M*)((const char*)&v + offset); } 
    M& operator()(T& v) const { return *(M*)((char*)&v + offset); } 
};

struct record
{
    int i;
    record(){ static int i_seq = 0; i = ++i_seq; }
};

int main(void)
{
    record rr[10];
    // если член известен на этапе компиляции
    proc_members(&rr[0], &rr[10], member<record, int, &record::i>());
    // если смещение известно на этапе компиляции
    proc_members(&rr[0], &rr[10], member_by_offset<record, int, offsetof(record, i)>());
    // если смещение неизвестно на этапе компиляции
    proc_members(&rr[0], &rr[10], member_by_offset<record, int>(offsetof(record, i)));
    return 0;
}
Re[6]: Доступ к полю неизвестной структуры
От: Masterkent  
Дата: 20.04.10 14:38
Оценка:
Kirikaza:

K>Т.е. из последней строки как бы следует, что указатель как раз и надо приводить через reinterpret_cast<char*>, чтобы добиться побайтовой адресной арифметики? Т.е. все реальные пацаны так и делают? Не приходилось мне драйвера писать, потому и спрашиваю.


Речь, скорее, о том, что побайтовое чтение через lvalue типа char или unsigned char не приводит к undefined behavior.

Что касается преобразований типов, то вот строгие формальные гарантии стандарта C++03, которые я вижу:

(1) Каждый объект некого типа T представлен последовательностью объектов типа unsigned char в количестве sizeof(T) штук (см. 3.9/4). Объект типа unsigned char представлен самим собой.
(2) Стандартное преобразование указателя на объект X к типу void* даёт нам указатель, указывающий на начало области памяти, где хранится объект X (см. 4.10/2), т.е. на самый первый объект типа unsigned char в представлении объекта X (см. (1)).
(3) При direct-инициализации указателя типа void* указателем типа T*, где T — объектный тип, выполняется стандартное преобразование — Pointer conversion (см. 8.5/14).
(4) static_cast<void *>(p), где p — указатель на объект, выполняет то же преобразование, которое имеет место при direct-initialization (см. 8.5/12, 5.2.9).
(5) сast-выражение (void *)p, где p — указатель типа cv T* (T — объектный тип), выполняет то же преобразование, что и const_cast<void *>(static_cast<cv void *>(p));
cast-выражение (T *)p, где T — объектный тип, а p — указатель типа void*, выполняет то же преобразование, что и static_cast<T *>(p) (см. 5.4/5).
(6) Значение указателя, полученного преобразованием const_cast, совпадает со значением исходного указателя (см. 5.2.11/3).
(7) Преобразование указателя p на объект X к типу void* посредством cast-expression вида (void *)p даёт нам указатель, указывающий на самый первый объект типа unsigned char в представлении объекта X (следствие из (2), (3), (4), (5) и (6)).
(8) Если указатель p типа unsigned char* указывает на самый первый объект типа unsigned char в представлении некоего объекта X типа T, то указатель (void *)p будет указывать на тот же самый объект типа unsigned char (следствие из (1) и (7)).
(9) Если указатель p типа unsigned char* указывает на самый первый объект типа unsigned char в представлении некоего объекта X типа T, а указатель q типа T* указывает на X, то (void *)p == (void *)q (следствие из (7) и (8)).
(10) Если указатель p типа cv T* указывает на объект, то static_cast<cv T *>(static_cast<cv void *>(p)) == p (см. 5.2.9/10).
(11) Если указатель p типа T* указывает на объект, то (T *)(void *)p == p (следствие из (5), (6) и (10)).
(12) Если указатель p типа T* указывает на объект X, то указатель (unsigned char *)(void *)p указывает на самый первый объект типа unsigned char в представлении объекта X (следствие из (9) и (11)).
(13) Если указатель p типа unsigned char* указывает на самый первый объект типа unsigned char в представлении некоего объекта X типа T, то указатель (T *)(void *)p указывает на объект X (следствие из (9) и (11)).

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

#include <cassert>
#include <cstddef>

template <class T, class CVSample>
    struct AddCVOf
        { typedef T type; };
template <class T, class U>
    struct AddCVOf<T, U const>
        { typedef T const type; };
template <class T, class U>
    struct AddCVOf<T, U volatile>
        { typedef T volatile type; };
template <class T, class U>
    struct AddCVOf<T, U const volatile>
        { typedef T const volatile type; };

template <class FieldType, class StructType>
    typename AddCVOf<FieldType, StructType>::type &
        get_field(StructType &x, std::size_t offset)
{
    typedef typename AddCVOf<FieldType, StructType>::type Result;
    return *(Result *)(void *)(((unsigned char *)(void *)&x) + offset);
}

struct A
{
    double d;
    int i;
};

int main()
{
    A a;
    A const &r = a;
    std::size_t offset = offsetof(A, i);
    get_field<int>(a, offset) = 123;
    assert(get_field<int>(r, offset) == 123);
    assert(a.i == 123);
}
Re[7]: Доступ к полю неизвестной структуры
От: Masterkent  
Дата: 20.04.10 14:41
Оценка:
Masterkent:

M>(1) Каждый объект некого типа T представлен последовательностью объектов типа unsigned char в количестве sizeof(T) штук (см. 3.9/4).


Ну, кроме базовых подобъектов и битовых полей
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.