1 2 3 4 5 6 7 8 9 10 11 12 13
| class Address {
int i ;
char b;
string c;
public:
void showMap ( void ) ;
};
void Address :: showMap ( void ) {
cout <<"address of int :" << &i << endl ;
cout <<"address of char :" << &b << endl ;
cout <<"address of string :" << &c << endl ;
} |
输出是:
1 2 3
| address of int : something
address of char : // nothing, blank area, that is nothing displayed
address of string : something |
为什么?
另一个有趣的事情:如果int、char、string是公共的,那么输出是
1 2 3
| ... int : something
... char :
... string : something_2 |
something_2 - something总是等于8。为什么?(不是9)
当你记下B的地址时,你就得到了1〔5〕。operator<<将其解释为C字符串,并尝试打印字符序列而不是其地址。
改为试试cout <<"address of char :" << (void *) &b << endl。
[编辑]和tomek评论的一样,在这种情况下使用更合适的演员阵容是static_cast,这是一个更安全的选择。以下是使用它而不是C样式转换的版本:
1
| cout <<"address of char :" << static_cast<void *>(&b) << endl; |
- 我建议在这里静态播放…
- 你能举例说明为什么静态演员表是一个更安全的选择吗?我不明白在这里使用void*有什么害处。
- @如果你知道b是什么,这里就没有一个。但是,当EDCOX1"6"不是你所想的那样,一个C++编译器会给你额外的安全性。一个C演员会盲目地做你所说的,而不是真正的关心,这是不好的。
- 这里的目的仅仅是为了灌输良好的实践,因为我仍然不知道为什么即使我不知道b是什么,也不能<(void*)&b是不好的?无论如何,它应该只打印地址,不是吗?
- @是的,避免C型铸造是一个很好的做法。这种情况下的行为是相同的。在其他情况下,&可能过载(因此您无法获得"地址"),或者您可能正在执行const/volatile-正确性问题,或者,或者,或者,或者,或者
有两个问题:
打印指针将打印int*和string*的地址,但不会打印char*的内容,因为operator<<中存在特殊过载。如果您需要地址,请使用:static_cast(&c);。
在你的平台上,sizeof(int)是4,sizeof(char)是1,所以你真的应该问为什么8不是5。原因是字符串在4字节边界上对齐。机器是用词而不是字节工作的,如果单词不是"分开"的,这里有几个字节和几个字节。这叫做对齐。
您的系统可能与4字节边界对齐。如果你有一个64位整数的64位系统,那么差异将是16。
(注:64位系统通常指的是指针的大小,而不是int。因此,具有4字节int的64位系统仍有8的差异,如4 + 1=5,但舍入到8。如果SIZEOF(int)为8,则8 + 1=9,但此回合最多为16)。
当您将一个字符的地址传输到一个Ostream时,它将解释为asciiz"c-style"字符串的第一个字符的地址,并尝试打印假定的字符串。您没有nul终止符,因此输出将一直尝试从内存中读取,直到它恰好找到一个终止符,或者操作系统因为尝试从无效地址读取而关闭它。它扫描的所有垃圾都将发送到输出。
你可以通过投射它来让它显示你想要的地址,就像在(void*)&b中那样。
将偏移量重设到结构中:您观察到字符串放置在偏移量8处。这可能是因为您有32位int,然后是8位char,然后编译器选择再插入3个8位char,这样字符串对象将在32位单词边界处对齐。许多CPU/内存体系结构需要指针、int等位于字大小边界上,以便对它们执行有效的操作,否则,在能够在操作中使用这些值之前,必须执行更多的操作来读取和组合内存中的多个值。取决于您的系统,可能是每个类对象都需要从单词边界开始,或者可能是std::string特别是从需要这种对齐的大小、指针或其他类型开始。
因为当您将一个char*传递给std::ostream时,它将打印它指向的C样式(即char数组、char*字符串)。
记住,"hello"是char*。
- "你好"是一个const char[6]。
- @msalters:不,它是char[6],使用时会在char*中腐烂。
- EDCOX1=1,仅在C中,而在C++中则是EDOCX1×0。有趣的是,它仍然可以衰变为char *,尽管(与c向后兼容)。
- @ HNNT:在C++ 03中被弃用,在C++ 11中完全删除。
char的地址被视为以nul结尾的字符串,并显示该地址的内容(可能未定义),但在本例中为空字符串。如果您将指针投射到void *,您将得到您想要的结果。
something2和something 8之间的差异是由于编译器能够自行决定变量在堆栈中的声明位置。
- 由于没有构造器,默认构造器不是自动创建的吗?默认构造器将设置b = 0,因此自动null终止?还+ 1
- @Muggen:上面的代码不完整,所以谁知道提供了什么构造函数。
- @muggen:no,生成的默认ctor不会对b进行零初始化。必须显式地执行该操作,例如:address()(临时)、new address()(与新地址比较)、address var=address()(在0x中)address var(我相信,需要进行双重检查)或具有静态存储持续时间(函数/命名空间/全局静态)的地址对象。
您的语法应该是
对于第二个问题-编译器默认将填充结构成员。默认的pad是sizeof(int),4字节(在大多数体系结构上)。这就是为什么一个int后跟一个char将在结构中占用8个字节,所以string成员的偏移量为8。
要禁用填充,请使用#pragma pack(x),其中x是以字节为单位的填充大小。
- 我怀疑由于对齐要求,打包只会使字符串的地址偏移5字节(在许多编译器上)。
- 数据对齐平台不是特定的吗?另外,afaik标准中,int不是4字节。
- @muggen-数据对齐确实是特定于平台的,但大多数情况下它是针对sizeof(int)--本机CPU大小。在32位CPU上,这是4个字节。
- @克里斯托弗-偏移量不是5字节,而是3字节。int从地址0到3。char应该是从4到5,而不是从4到7。最后,string从8开始。
- @ Eli上的许多64位CPU EDOCX1的14度仍然是4。
- EELI:EDCOX1×16是字节4。字节5到7是填充,而不是EDCOX1×16 16的一部分,根据定义,EDOCX1是21。相对于包围对象的开头,我指的是5的偏移量。
- @ KitsuneYMG:这实际上是编译器编写者和运行时环境作者的决定,而不是CPU的决定。(在64位Linux和64位Windows之间,同一硬件上的EDCOX1×22)不同。
- @Kitsune-我查过了,你看起来是对的。我在64位体系结构方面没有太多经验…谢谢你的提醒
- @克里斯托弗-当然,这正是我的意思-字符被填充到4个字节。
- @伊莱:我很确定我们的意思是一样的,但我现在有点吹毛求疵了:char没有衬垫。将填充封闭对象,并在char之后插入填充(三个字节)。
- @克里斯托弗-我明白你的意思,我同意。
- 我相信这些定义(以字节为单位):[unsigned signed]char>=1,short>char,long>short,short<=int<=long。这就是为什么我几乎总是使用来自cstdint的确切大小类型(如果大小很重要)。
- @基斯图尼姆:c99在6.5.3.4.3中规定了sizeof(unsigned char) == sizeof(signed char) == sizeof(char) == 1。
HRNT对空白的原因是正确的:EDCOX1〔0〕具有EDCOX1型(1),因此被打印为字符串,直到第一个零字节。推测b为0。如果设置EDOCX1,2,to,例如,"A",那么你应该期望打印输出是一个从"A"开始的字符串,然后继续到下一个零字节。使用EDCOX1×4来打印它作为一个地址。
对于第二个问题,&c - &i是8,因为int的大小是4,char是1,字符串从下一个8字节边界开始(您可能在64位系统上)。每种类型都有一个特定的对齐方式,C++根据它对结构中的字段进行对齐,适当地添加填充。(经验法则是,大小为n的原始字段与n的倍数对齐)特别是,您可以在b之后再添加3个char字段,而不会影响地址&c。