Realloc () / Изменение размера объекта в C ++ для реализации строки - PullRequest
0 голосов
/ 16 марта 2009

Когда они представлены в памяти, объекты C ++ совпадают со структурами C?

Например, с C я мог бы сделать что-то вроде этого:

struct myObj {
       int myInt;
       char myVarChar;
};

int main() {
       myObj * testObj = (myObj *) malloc(sizeof(int)+5);
       testObj->myInt = 3;
       strcpy((char*)&testObj->myVarChar, "test");
       printf("String: %s", (char *) &testObj->myVarChar);
}

Я не думаю, что C ++ позволяет перегрузить оператор + для встроенного типа char *.

Итак, я хотел бы создать свой собственный легкий класс строк, который не имеет дополнительных издержек, которые есть у std::string. Я думаю, что std::string представляется смежно:

(int)length, (char[])data

Мне нужна точно такая же функциональность, но без префикса длины (экономия 8 байтов).

Вот код, который я использую для тестирования, но это приводит к segfault

#include <iostream>
using namespace std;
class pString {
    public:
        char c;
        pString * pString::operator=(const char *);
};


pString * pString::operator=(const char * buff) {

    cout << "Address of this: " << (uint32_t) this << endl;
    cout << "Address of this->c: " << (uint32_t) &this->c << endl;

    realloc(this, strlen(buff)+1);
    memcpy(this, buff,  strlen(buff));
    *(this+strlen(buff)) = '\0';

    return this;
};

struct myObj {
        int myInt;
        char myVarChar;
};

int main() {

    pString * myString = (pString *) malloc(sizeof(pString));
    *myString = "testing";
    cout << "'" << (char *) myString << "'";    
}

Редактировать: никто действительно не понимает, что я хочу сделать. Да, я знаю, что могу иметь указатель на строку в классе, но это на 8 байт дороже, чем простая строка, я хотел точно такое же внутреннее представление. Спасибо за попытку, хотя


Редактировать: конечным результатом того, чего я хотел достичь, была возможность использовать оператор + без использования дополнительной памяти по сравнению с использованием strcat и т. Д.

const char * operator+(const char * first, const char * second);

Ответы [ 17 ]

16 голосов
/ 16 марта 2009

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

Например, ваш код имеет квадратичную сложность для перераспределений памяти в операторе присваивания - каждое назначение строки, больше на 1 символ, будет использовать новый блок памяти, больший на 1 байт, что приведет к большой фрагментации памяти после «нескольких» назначений, подобных этому - вы экономите несколько байтов, но потенциально теряете мегабайты из-за адресного пространства и фрагментации страницы памяти.

Кроме того, проектируя этот способ, у вас нет способа эффективно реализовать оператор + =, поскольку вместо простого копирования добавленной строки в большинстве случаев вам всегда нужно будет копировать всю строку - таким образом снова достигается квадратичная сложность в случае добавления небольших строк. несколько раз больше.

Извините, но ваша идея, похоже, имеет большие шансы стать ужасной в обслуживании и на несколько порядков менее эффективной, чем типичные реализации строк, такие как std :: string.

Не волнуйтесь - это практично для всех отличных идей «написания собственной лучшей версии стандартного контейнера»:)

8 голосов
/ 16 марта 2009
struct myObj {
   //...
   char myVarChar;
};

Это не сработает. Вам нужен либо массив фиксированного размера, указатель на тип char, либо использование структуры hack. Вы не сможете назначить указатель на этот myVarChar.

так что я хотел бы создать свой собственный легкий класс строк, который не имеет никаких дополнительных издержек std :: string has.

Какие дополнительные накладные расходы вы имеете в виду? Вы проверяли и измеряли, является ли std::string узким местом?

Я думаю, что std :: string представляется смежно

Да, в основном, часть символьного буфера. Тем не менее, следующее:

(интермедиат) длина (символ []) данные

не требуется стандартом. Переведено: Строковая реализация не должна использовать этот конкретный макет своих данных. И может иметь дополнительные данные.

Теперь ваш легкий класс строк содержит ошибки:

class pString {
public:
    char c; // typically this is implementation detail, should be private
    pString * pString::operator=(const char *); 
    // need ctors, dtors at least as well
    // won't you need any functions on strings?
};

Попробуйте что-то вроде следующего:

/* a light-weight string class */
class lwstring { 
  public:
     lwstring(); // default ctor
     lwstring(lwstring const&); // copy ctor
     lwstring(char const*); // consume C strings as well
     lwstring& operator=(lwstring const&); // assignment
     ~lwstring(); // dtor
     size_t length() const; // string length
     bool empty() const; // empty string?
  private:
     char *_myBuf;
     size_t _mySize;
};
6 голосов
/ 16 марта 2009

Ничего себе. То, что вы пытаетесь сделать, - это полное злоупотребление C ++, полностью зависимое от компилятора, если оно сработает, и наверняка когда-нибудь попадет в TheDailyWTF.

Причина, по которой вы получаете ошибку segfault, возможно, в том, что ваш оператор = перераспределяет объект по другому адресу, но вы не обновляете указатель myString в main. Я стесняюсь даже называть это объектом в этот момент, так как ни один конструктор никогда не вызывался.

Я думаю, что вы пытаетесь сделать pString более умным указателем на строку, но вы все делаете неправильно. Позвольте мне взять трещину в этом.

#include <iostream>
using namespace std;
class pString {
    public:
        char * c;
        pString & operator=(const char *);
        const char * c_str();
};


pString & pString::operator=(const char * buff) {

    cout << "Address of this: " << (uint32_t) this << endl;
    cout << "Address of this->c: " << (uint32_t) this->c << endl;

    c = (char *) malloc(strlen(buff)+1);
    memcpy(c, buff,  strlen(buff));
    *(c+strlen(buff)) = '\0';

    return *this;
};

const char * pString::c_str() {
    return c;
}

int main() {

    pString myString;
    myString = "testing";
    cout << "'" << myString.c_str() << "'";    

}

Теперь я бы не использовал malloc, а new / delete вместо этого, но я оставил это как можно ближе к вашему оригиналу.

Вы могли бы подумать , что вы тратите пространство указателя в своем классе, но это не так - вы торгуете им за указатель, который ранее оставался в main. Надеюсь, этот пример проясняет ситуацию - переменные имеют одинаковый размер, и объем дополнительной памяти, выделенной malloc / realloc, также одинаков.

pString myString;
char * charString;
assert(sizeof(myString) == sizeof(charString));

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

2 голосов
/ 16 марта 2009

когда они представлены в памяти, объекты C ++ объекты такие же, как структуры C.

Строго говоря, нет. В общем да. Классы и структуры C ++ идентичны по структуре памяти структурам C за исключением:

  • Битовые поля имеют разные правила упаковки
  • Размеры фиксируются во время компиляции
  • Если есть какие-либо виртуальные функции, компилятор добавит запись vtable в схему памяти.
  • Если объект наследует базовый класс, макет нового класса будет добавлен к макету базового класса, включая vtable, если таковой имеется.

Я не думаю, что C ++ позволяет перегрузить оператор + для встроенного типа char *. поэтому я хотел бы создать свой собственный легкий класс строк, который не имеет дополнительных издержек, которые имеет std :: string. Я думаю, что std :: string представляется смежно

Вы можете создать перегрузку operator+ для типа char*. Нормальное поведение - арифметика указателя. std::string перегружает operator+, чтобы добавить char* данные в строку. Строка хранится в памяти как строка C плюс дополнительная информация. Функция-член c_str() возвращает указатель на внутренний массив char.

В вашем примере C вы полагаетесь на неопределенное поведение. Не realloc так. Это может привести к плохим вещам, а именно к причудливым последствиям.

Ваш пример C ++ также делает плохие вещи при выполнении realloc(this). Вместо этого вы должны иметь char* и получить new char[] буфер для хранения символов вместо realloc(). Поведение для такого realloc не определено.

2 голосов
/ 17 марта 2009

Я не думаю, что «это» работает так, как вы думаете.

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

Очевидный путь к этому заключается в том, что ваш класс должен содержать указатель на буфер и перераспределять его. Тем не менее, переопределение строкового класса - это хороший способ избавиться от головной боли. Простая функция-обертка, вероятно, выполнила бы то, что вы хотели (предполагая, что «возможность использовать оператор + без дополнительного использования памяти по сравнению с использованием strcat» действительно то, что вы хотели):

void concatenate(std::string& s, const char* c) {
    s.reserve(s.size() + strlen(c));
    s.append(c);
}

Есть некоторая вероятность, что append может сделать это внутренне в любом случае.

2 голосов
/ 16 марта 2009

Почему вы пишете в C с классами, почему не используете C ++?

2 голосов
/ 16 марта 2009

Существует много ошибок в определении / использовании вашего класса. Если вы хотите сохранить строку, вы должны использовать тип указателя, например, char * a member, а не отдельный char. Использование одного символа означает, что выделяется только один символ памяти.

Другая ошибка - это код выделения, когда вы делаете реаллок на этом - вы можете изменить выделенную память, но не значение этого. Вы должны присвоить результат этому, чтобы достичь этого (this = (*pString)realloc(this, strlen(buff+1));), и это все равно очень плохая практика. Намного лучше использовать realloc на элементе char *.

К сожалению, собственно C ++ не имеет альтернативы для realloc или expand , и вместо этого вы должны использовать new и delete, копируя самостоятельно.

2 голосов
/ 16 марта 2009

Вы не можете изменить размер объекта / структуры в C или C ++. Их размеры фиксируются во время компиляции.

1 голос
/ 16 марта 2009

Вы перемещаете указатель «это». Это не будет работать. Я думаю, что вы действительно хотите, это просто обертка вокруг буфера.

1 голос
/ 16 марта 2009

не обращайте внимания на отсутствие корректности констант, так как это макет, но как на счет этого:

class light_string {
public:
    light_string(const char* str) {
        size_t length = strlen(str);
        char*  buffer = new char[sizeof(size_t) + length + 1];

        memcpy(buffer, &length, sizeof(size_t));
        memcpy(buffer + sizeof(size_t), str, length);
        memset(buffer + sizeof(size_t) + length, 0, 1);

        m_str = buffer + sizeof(size_t);
    }

    ~light_string() {
        char* addr = m_str - sizeof(size_t);
        delete [] addr;
    }

    light_string& operator =(const char* str) {
        light_string s = str;
        std::swap(*this, s);

        return *this;
    }

    operator const char*() {
        return m_str;
    }

    size_t length() {
        return
            *reinterpret_cast<size_t *>(m_str - sizeof(size_t));
    }

private:
    char* m_str;
};


int main(int argc, char* argv[]) 
{
    cout<<sizeof(light_string)<<endl;

    return 0;
}
...