Realloc()/在C ++中调整对象的大小以实现字符串实现

时间:2009-03-16 18:55:53

标签: c++ realloc

当它们在内存中表示时,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个字节的开销)。

这是我用来测试的代码,但它导致了段错误

#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 << "'";    
}

编辑:没有人真正了解我想做什么。是的我知道我可以有一个指向类中字符串的指针,但比普通的cstring贵8个字节,我想要完全相同的内部表示。谢谢你试试


编辑:我想要实现的最终结果是能够使用+运算符,与使用strcat等相比没有额外的内存使用

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

17 个答案:

答案 0 :(得分:16)

你不应该浪费你的时间来编写字符串类 - 这是人们花时间编写它们的原因,并且认为他们编写它们是天真的,因为他们想要创建大的混淆和过度的代码,你可以很容易地改进它们。小时问题。

例如,您的代码在赋值运算符中的内存重新分配具有二次复杂度 - 每次分配大于1个字符的sting将使用大于1个字节的新内存块,导致在“少量”分配之后出现大量内存碎片 - 您节省了几个字节,但可能会丢失兆字节来解决空间和内存页碎片问题。

同样以这种方式设计你无法有效地实现+ =运算符,而不是仅仅复制附加的字符串,在大多数情况下你总是需要复制整个字符串 - 因此如果你追加小字符串,再次达到二次复杂度几次到一个更大的。

很抱歉,但是你的想法看起来很有可能变得难以维持,并且比std :: string这样的典型字符串实现效率低一些。

别担心 - 对于“编写自己更好的标准容器版本”的所有好主意来说都是如此:)

答案 1 :(得分:8)

struct myObj {
   //...
   char myVarChar;
};

这不起作用。您需要一个固定大小的数组,一个指向char的指针或使用struct hack。您将无法为此myVarChar分配指针。

  

所以我想创建自己的轻量级字符串类,它没有额外的开销std :: string has。

你指的是多少额外开销?您是否测试并测量过std::string是否真的成为瓶颈?

  

我认为std :: string是连续表示的

是的,主要是字符缓冲部分。但是,以下内容:

  

(int)的长度(字符[])的数据

标准不要求

。翻译:字符串实现不需要使用其数据的这种特定布局。它可能还有其他数据。

现在,您的轻量级字符串类出现了错误:

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;
};

答案 2 :(得分:6)

哇。你要做的是彻底滥用C ++,如果它有效的话,将完全依赖于编译器,并且肯定会让你有一天在TheDailyWTF登陆。

你得到段错误的原因可能是因为你的operator =正在将对象重新分配到不同的地址,但是你没有更新main中的myString指针。我甚至在这一点上甚至称它为一个对象,因为从未调用任何构造函数。

我认为你要做的是让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,但我保留了尽可能接近原始内容。

可能会认为你是在浪费你班级中指针的空间,但你不是 - 你正在为你之前保留在主要指针中的指针进行交易。我希望这个例子说清楚 - 变量大小相同,malloc / realloc分配的额外内存量也是一样的。

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

P.S。我应该指出,这段代码仍然需要很多工作,它充满了漏洞。你需要一个构造函数来初始化指针,一个析构函数可以在它完成时释放它,只是为了初学者。你也可以自己实现operator +。

答案 3 :(得分:2)

  

当它们在内存中表示时,对象C ++对象与C结构相同。

严格来说,没有。一般来说,是的。 C ++类和结构在内存布局上与C结构相同,除了:

  • 位字段具有不同的打包规则
  • 大小在编译时修复
  • 如果有任何虚函数,编译器会在内存布局中添加一个vtable条目。
  • 如果对象继承了基类,则新类的布局将附加到基类布局,包括vtable(如果有)。
  

我不认为C ++允许重载内置char *类型的+运算符。所以我想创建自己的轻量级字符串类,它没有额外的开销std :: string has。我认为std :: string是连续表示的

您可以为operator+类型创建char*重载。正常行为是指针算术。 std::string重载operator+以将char*数据附加到字符串。该字符串作为C字符串存储在内存中,另外还有其他信息。 c_str()成员函数返回指向内部char数组的指针。

在您的C示例中,您依赖于未定义的行为。不要realloc那样。它可能导致糟糕的事情 - 即奇怪的段错误。

你的C ++示例在执行realloc(this)时也做了坏事。相反,您应该携带char*并获取new char[]缓冲区来存储字符而不是realloc()。这种realloc的行为未定义。

答案 4 :(得分:2)

您的班级定义/用法存在很多问题。如果要存储字符串,则应使用指针类型,如char * a member,而不是单个char。使用单个char意味着只分配了一个内存字符。

另一个错误是你对此进行重新分配的分配代码 - 你可以改变分配的内存,但不能改变它的值。您必须将结果分配给此以实现此目的(this = (*pString)realloc(this, strlen(buff+1));),这无论如何都是非常糟糕的做法。在char *成员上使用realloc要好得多。

不幸的是C ++本身有no alternative for realloc or expand,你必须改为使用new和delete,自己做任何复制。

答案 5 :(得分:2)

为什么用C语言编写C语言,为什么不使用C ++?

答案 6 :(得分:2)

您无法在C或C ++中更改对象/结构的大小。它们的大小在编译时是固定的。

答案 7 :(得分:2)

我认为'this'不会像你认为的那样有效。

具体来说,你不能重新分配它以指向成员函数中的更大缓冲区,因为无论什么叫成员函数仍然有一个指向旧'this'的指针。由于它没有通过引用传递,因此无法更新它。

显而易见的是,你的类应该拥有一个指向缓冲区的指针并重新分配它。但是,重新实现字符串类是一种很好的方法,可以给自己带来很多麻烦。一个简单的包装函数可能会实现你想要的(假设“与使用strcat相比,能够使用没有额外内存使用量的+运算符”真的是你想要的):

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

无论如何,追加可能会在内部做到这一点。

答案 8 :(得分:1)

您无法重新分配C ++对象。正如其他人所指出的那样this并不是一个你可以修改的指针,但不能保证它会指向一个区域realloc具有访问权限。

连接的一个解决方案是实现一个类层次结构,它将推迟实际连接直到需要它。

像这样的东西

class MyConcatString;
class MyString {
public:
  MyString(const MyConcatString& c) {
    reserve(c.l.length()+c.r.lenght());
    operator = (l);
    operator += (r);   
  }
  MyConcatString operator + (const MyString& r) const {
    return MyConcatString(*this, r);
  }
};

class MyConcatString {
public:
  friend class MyString;
  MyConcatString(const MyString& l, const MyString& r):l(l), r(r) {};
  ...
  operator MyString () {
    MyString tmp;
    tmp.reserve(l.length()+r.length());
    tmp = l;
    tmp += r;
    return tmp;
  }
private:
  MyString& l;
  MyString& r;
}

所以,如果你有

MyString a = "hello";
MyString b = " world";
MyString c = a + b;

会变成     MyString c = MyConcatString(a,b);

有关详细信息,请查看“C ++编程语言”。

其他解决方案是将char *包装在struct中,但你似乎不喜欢这个想法。

但无论您选择何种解决方案,C ++中的对象都无法重新定位。

答案 9 :(得分:1)

您想要做的不是也不能在C ++中工作。您正在寻找的是灵活阵列的C99功能。这在C99中运行良好有两个原因,首先你没有内置构造函数,第二个你没有继承(至少不是语言特性)。如果一个类继承自另一个类,则子类使用的内存由后面的父类的内存打包,但灵活的数组需要在结构/类的末尾。

class pString {
    char txt[];
}

class otherString : pString { // This cannot work because now the
    size_t len;               // the flexible array is not at the
}                             // end
拿pd :: string它是由C ++专家编写的,我敢肯定他们没有理由没有留下“好招”。如果您仍然发现它们在您的程序中表现不佳,请使用纯C字符串,当然,它们不提供您想要的甜蜜API。

答案 10 :(得分:1)

#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;

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

    cout << "Address of this After: " << (uint32_t) newPoint << endl;

    return (pString*)newPoint;
};

int main() {

    pString * myString = (pString *) malloc(sizeof(pString));
    *myString = "testing";

    cout << "Address of myString: " << (uint32_t) myString << endl;

    cout << "'" << (char *) myString << "'";    
}

Works当realloc不重新分配指针时,即

  

地址:1049008   地址 - >&gt; c:1049008   此地址后:1049008   myString的地址:1049008'testing'

可以使用,但是当发生以下情况时,它会失败

  

地址:1049008   地址 - >&gt; c:1049008   此地址:1049024   myString的地址:1049008''

显而易见的解决方案是

this = (pString*) newPoint;

但编译器抱怨赋值中的左值无效。有没有人更新这个正确的方法(只是为了完整性,我怀疑我会使用代码,因为每个人似乎都讨厌它)。感谢

答案 11 :(得分:1)

您正在移动“this”指针。那不行。 我认为你真正想要的只是缓冲区的包装。

答案 12 :(得分:1)

不介意缺乏const正确性,因为这是一个模拟,但是如何:

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;
}

答案 13 :(得分:1)

 #include <iostream>
    using namespace std;
    class pString {
        public:
            char c[1];
            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->c, strlen(buff)+1);
        memcpy(this->c, buff,  strlen(buff));
        *(this->c+strlen(buff)) = '\0';

        return this;
    };

    struct myObj {
            int myInt;
            char myVarChar;
    };

    int main() {

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


This should work. But its not advisable.

答案 14 :(得分:1)

如果你想要一些与std::string基本相同的东西,除了它不知道字符串有多长,你应该知道std::string如何工作,它有多少运算符重载等等。然后模仿,只需要你想要的差异。

然而,不太可能有任何真正的意义。

关于您的最新更新 - 您说您想要一种设计,其中一般应用程序代码将传递给堆对象的裸指针。没有自动清理。

这很简单,这是一个非常糟糕的主意。

答案 15 :(得分:0)

这段代码很乱,RnR和其他人建议不建议。但它适用于我想要它做的事情:

#include <iostream>
using namespace std;

struct pString {
        /* No Member Variables, the data is the object */ 
        /* This class cannot be extended & will destroy a vtable */
    public:
        pString * pString::operator=(const char *);
};

pString& operator+(pString& first, const char *sec) {


        int lenFirst;
        int lenSec = strlen(sec);
        void * newBuff = NULL;

        if (&first == NULL)
        {
            cout << "NULL" << endl;
            lenFirst = 0; 
            newBuff = malloc(sizeof(pString)+lenFirst+lenSec+1);
        } else {
            lenFirst = strlen((char*)&first);
            newBuff= (pString*)realloc(&first, lenFirst+lenSec+1);
        }

        if (newBuff == NULL)
        {
            cout << "Realloc Failed"<< endl;
            free(&first);
            exit(0);
        }       

        memcpy((char*)newBuff+lenFirst, sec, lenSec);
        *((char*)newBuff+lenFirst+lenSec) = '\0';


        cout << "newBuff: " << (char*)newBuff << endl;

        return *(pString*)newBuff;

};


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

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

    char *newPoint = (char *)realloc(this, strlen(buff)+200);
    memcpy(newPoint, buff,  strlen(buff));
    *((char*)newPoint+strlen(buff)) = '\0';

    cout << "Address of this After: " << (uint32_t) newPoint << endl;

    return (pString*)newPoint;
};


int main() {

    /* This doesn't work that well, there is something going wrong here, but it's just a proof of concept */

    cout << "Sizeof: " << sizeof(pString) << endl;

    pString * myString = NULL;

    //myString = (pString*)malloc(1);
    myString = *myString = "testing";
    pString& ref = *myString;


    //cout << "Address of myString: " << myString << endl;

    ref = ref + "test";
    ref = ref + "sortofworks" + "another" + "anothers";


    printf("FinalString:'%s'", myString);

}

答案 16 :(得分:0)

如果你想要表现,你可以像这样编写你的课程:

template<int max_size> class MyString
{
public:
   size_t size;
   char contents[max_size];

public:
   MyString(const char* data);
};

在上下文中将max_size初始化为适当的值。通过这种方式,可以在堆栈上创建对象,并且不涉及内存分配。

可以通过重载新运算符来创建所需的内容:

class pstring
{
public:
    int myInt;
    char myVarchar;

    void* operator new(size_t size, const char* p);
    void operator delete(void* p); 
};

void* pstring::operator new(size_t size, const char* p)
{
    assert(sizeof(pstring)==size);
    char* pm = (char*)malloc(sizeof(int) + strlen(p) +1 );
    strcpy(sizeof(int)+pm, p);
    *(int*)(pm) = strlen(p);  /* assign myInt */
    return pm;
}

void pstring::operator delete(void* p)
{
    ::free(p);
}


pstring* ps = new("test")pstring;

delete ps;