0

当它们在内存中表示时,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);
4

17 回答 17

16

您不应该浪费时间编写字符串类 - 人们首先花时间编写它们是有原因的,并且认为他们编写它们是因为他们想要创建大的混淆和开销代码,您可以在几个小时内轻松改进这些代码是天真的.

例如,您的代码在赋值运算符中的内存重新分配具有二次复杂度 - 每个大于 1 个字符的字符串分配将使用一个大于 1 个字节的新内存块,从而在像这样的“少数”分配后导致大的内存碎片 - 你保存几个字节,但可能会丢失兆字节来地址空间和内存页面碎片。

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

抱歉,但您的想法看起来很可能变得难以维护,并且效率比典型的字符串实现(如 std::string)低几个数量级。

别担心——这对于“编写自己的标准容器的更好版本”的所有伟大想法都是如此:)

于 2009-03-16T19:22:05.753 回答
8
struct myObj {
   //...
   char myVarChar;
};

这行不通。你要么需要一个固定大小的数组,一个指向 char 的指针,要么使用 struct hack。您将无法分配指向 this 的指针myVarChar

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

你指的是什么额外的开销?你有没有测试和测量,看看是否std::string真的是一个瓶颈?

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

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

(int)长度(char[])数据

标准没有要求。已翻译:字符串实现不需要使用其数据的这种特定布局。它可能有额外的数据。

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

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;
};
于 2009-03-16T19:06:31.127 回答
5

哇。您正在尝试做的是完全滥用 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 ,但我将其尽可能接近您的原始版本。

可能认为您在浪费类中指针的空间,但事实并非如此——您正在用它来换取您之前保存在 main 中的指针。我希望这个例子能说明问题——变量大小相同,malloc/realloc 分配的额外内存量也相同。

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

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

于 2009-03-16T19:52:42.437 回答
2

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

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

不幸的是,C++ 本身没有 realloc 或 expand 的替代方法,您必须使用 new 和 delete,自己进行任何复制。

于 2009-03-16T19:09:52.790 回答
2

为什么你用 C 编写类,为什么不使用 C++?

于 2009-03-16T19:18:42.813 回答
2

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

于 2009-03-16T18:58:46.060 回答
2

当它们在内存中表示时是对象 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)​​ . 相反,您应该携带 achar*并获取一个new char[]缓冲区来存储字符而不是 a realloc()。此类 arealloc的行为未定义。

于 2009-03-16T19:05:09.243 回答
2

我不认为“这个”像你认为的那样起作用。

具体来说,您不能重新分配 this 以指向成员函数中的更大缓冲区,因为调用该成员函数的任何内容仍然具有指向旧“this”的指针。由于它不是通过引用传递的,因此您无法更新它。

解决这个问题的明显方法是你的类应该持有一个指向缓冲区的指针并重新分配它。然而,重新实现一个字符串类是一个让自己很头疼的好方法。一个简单的包装函数可能会完成您想要的(假设“与使用 strcat 相比,能够使用 + 运算符而没有额外的内存使用”确实是您想要的):

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

无论如何,附加可能会在内部执行此操作。

于 2009-03-16T21:59:17.593 回答
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 << "'";    
}

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

此地址:1049008 此->c 的地址:1049008 此之后的地址:1049008 myString 的地址:1049008 'testing'

有效,但是当发生以下情况时它会失败

this的地址:1049008 this->c的地址:1049008 this的地址After:1049024 myString的地址:1049008''

显而易见的解决方案是

this = (pString*) newPoint;

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

于 2009-03-16T21:38:11.403 回答
1

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

于 2009-03-16T20:13:22.730 回答
1

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

然而,这不太可能有任何实际意义。

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

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

于 2009-03-16T19:21:31.403 回答
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.
于 2009-03-16T19:51:36.137 回答
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;
}
于 2009-03-16T20:09:59.510 回答
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

以 std::string 为例,它是由 C++ 专家编写的,我相信他们不会无缘无故遗漏一个“好把戏”。如果您仍然发现它们在您的程序中表现不佳,请改用纯 C 字符串,当然,它们不提供您想要的甜蜜 API。

于 2009-03-16T23:40:44.680 回答
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* 包装在结构中,但您似乎不喜欢这个想法。

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

于 2009-03-16T23:57:54.843 回答
0

如果你想要表现,你可以这样写你的类:

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

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

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

可以通过重载 new 运算符来创建您想要的内容:

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;
于 2009-03-16T19:23:00.933 回答
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);

}
于 2009-03-16T23:20:02.033 回答