地址转换为 int 指针后,打印出的是字符串第一个字符的地址 还是字符串开始的地址?

2017-12-26 09:24:33 +08:00
 RDF
地址转换为 int 指针后,打印出的是字符串第一个字符的地址 还是字符串开始的地址?
cout << (int *)"Home of the jolly bytes"<<endl;
2809 次点击
所在节点    程序员
37 条回复
RDF
2017-12-27 15:55:56 +08:00
@gnaggnoyil c++ 指针可以直接以数组形式使用。
RDF
2017-12-27 15:57:21 +08:00
@gnaggnoyil 参考 C++11 核心定义
gnaggnoyil
2017-12-27 18:19:00 +08:00
@RDF array to pointer decay 只会在有限的几个场景(比如函数传参)中使用,你这里在传参之前已经做 cast 了,所以这个 cast 是 UB.

而且就算你拿 const char *来,把它 cast 到 int *都是 UB.
RDF
2017-12-27 18:31:15 +08:00
@gnaggnoyil 不可能是未定义,因为这在 c11 的概念中是有明确定义的:双引号内的为字符串。cout 打印时读取字符串第一个字符的内存地址。并将其认成字符串进行输出。这是一般情况下直接用双引号进行输出时的明确定义。( int*)是对地址的转义进行强制的格式转换。使其可以输出首字符所在内存的内存地址。而不是直接将字符串的首字符内存地址认成字符串输出而进行字符串输出。
这是有明确定义的行为。
gnaggnoyil
2017-12-27 18:51:26 +08:00
@RDF 你要明确定义?好,你可以去翻翻标准[expr.cast]章节看看把 const char [N]转换成 int *是什么样的行为,而这并不取决于你如何用转换之后的值.

如果你要取得指针本身所包含的值,标准规定的唯一正确的方法是将其 reinterpret_cast 到 std::uintptr_t.

顺便说下虽然规定的具体用语有差别但是从 C++98 以来到现在乱转型实际结果大致都是不变的——不是错误就是会引起 UB.C 的规定可能比 C++要宽松些,不过我对 C 不熟所以不发表评论.
RDF
2017-12-27 19:37:37 +08:00
@gnaggnoyil 就是 c++11 修订的明确定义
RDF
2017-12-27 19:46:12 +08:00
@gnaggnoyil
The expression "Home of the jolly bytes" is a string constant;
hence it evaluates as the address of the beginning of the string.The cout object
interprets the address of a char as an invitation to print a string, but the type cast
(int *) converts the address to type pointer-to-int, which is then printed as an
address. In short, the statement prints the address of the string, assuming the int
type is wide enough to hold an address.

对于,(int *) ,不存在未定义的问题。 只是讨论其定义的实际区间问题。
RDF
2017-12-27 20:11:05 +08:00
C++11
对 cout <<"Home of the jolly bytes"<<endl;
读取字符串"Home of the jolly bytes"所在内存,开始位置的内存地址(即字符 H 存储的内存的地址),cout 读取到字符串地址时,直接打印由该地址处开始的字符,直到\0;
打印 Home of the jolly bytes

对于
cout << (int *)"Home of the jolly bytes"<<endl;
则是对字符串"Home of the jolly bytes"中,开始字符 H 的内存地址进行解除引用,以打印出其所在内存的内存地址,而非‘由字符串内存地址开始打印字符’。 但疑惑的是, 这个地址,在数值上, [字符串的开始的内存地址的值] 和 [字符串首字符的内存地址的值] 在数值上相同。如果是直接声明的字符串 short ccc [8] ,可以
cout<<ccc <<endl; 即&ccc[0],其指向一个 short 内存块地址( 2bit ),形如* short
cout<<ccc+1 <<endl;

cout<<&ccc <<endl;直接指向了含有 16bit 的 short 数组,+1 使其向后指了 16bit
cout<<&ccc+1 <<endl;
借由+1 进行

但疑惑的是,对字符串取( int*) 查看到的内存地址, 实际表述为 [字符串的开始的内存地址的值] 还是 [字符串首字符的内存地址的值] 。不超 int 范围的前提下, 取法没问题, 但是这个对象没有找到清晰的解释。
RDF
2017-12-27 20:34:29 +08:00
@gnaggnoyil
数组名被解释为其第一个元素的地址。
对数组名应用地址运算符时,得到的是整个数组的地址
对于,cout<<"statement"<<endl; cout 对象对所认为的字符串地址,直接打印该地址处的字符,强制*解除引用 则得到地址的值,但是由于
{
在 cout 和多数 C++表达式中,char 数组名,char 指针,以及用引号括起的字符串常量都被解释为 字符串第一个字符的地址。
}

因此
{
如果给 cout 提供一个指针,它将打印地址,如果指针类型为 char *,则显示指向的字符串
而 int* 便是将字符串指针*强制转换为另一种类型来进行显示。
}
RDF
2017-12-27 20:46:25 +08:00
@gnaggnoyil
即有:
cout << "a"<< endl; 打印 a
cout << *"a"<< endl; 打印 a [此时为 char 指针]
cout << (int *) "a" << endl; 打印"a"字符串( a 和'\0' )中第一个字符的地址。

-----------------------------------------
cout << (int *)*"a" << endl;对 a 的地址解除引用得到 a 的值,由于上文, [此时为 char 指针] ,对其再次给一个强制转换的指针,以直接打印 a 在 ASCII 系统上的 ASCII 编码,61
RDF
2017-12-28 08:39:21 +08:00
@gnaggnoyil
即,对:
#include "stdafx.h"
#include <iostream>
const int ArSize = 20;
int main()
{
using namespace std;
char name[ArSize];

cout << "ASCIIized: ";
cin >> name;
cin.get();
cout << "ASCIIized:\n";
int i = 0;
while (name[i] != '\0')
{
cout << name[i] << ": " << int(name[i]) << endl;

cout << name[i] << ": " << (int *)*name << endl;
cout << name[i] << ": " << (int*)(*name + 1) << endl;

cout << name[i] << ": " << (int *)name<< endl;
cout << name[i] << ": " << (int*)(name+1) << endl;

i++;
}
cin.get();
}


有:

ASCIIized: abc
ASCIIized:
a: 97
a: 00000061
a: 00000062
a: 0053FB6C
a: 0053FB6D
b: 98
b: 00000061
b: 00000062
b: 0053FB6C
b: 0053FB6D
c: 99
c: 00000061
c: 00000062
c: 0053FB6C
c: 0053FB6D
RDF
2017-12-28 08:41:54 +08:00
@gnaggnoyil

即,对:
#include "stdafx.h"
#include <iostream>
const int ArSize = 20;
int main()
{
using std::cin;
using std::endl;
using std::cout;
char name[ArSize];

cout << "ASCIIized: ";
cin >> name;
cin.get();
cout << "ASCIIized:\n";
int i = 0;
while (name[i] != '\0')
{
cout << name[i] << ": " << int(name[i]) << endl;

cout << name[i] << ": " << (int *)*name << endl;
cout << name[i] << ": " << (int*)(*name + 1) << endl;

cout << name[i] << ": " << (int *)name<< endl;
cout << name[i] << ": " << (int*)(name+1) << endl;

i++;
}
cin.get();
}


有:

ASCIIized: abc
ASCIIized:
a: 97
a: 00000061
a: 00000062
a: 0053FB6C
a: 0053FB6D
b: 98
b: 00000061
b: 00000062
b: 0053FB6C
b: 0053FB6D
c: 99
c: 00000061
c: 00000062
c: 0053FB6C
c: 0053FB6D
RDF
2017-12-28 08:55:44 +08:00
上文的表述形式少做修改,并显式表达:


#include "stdafx.h"
#include <iostream>
const int ArSize = 20;
int main()
{
using std::cin;
using std::endl;
using std::cout;
char name[ArSize];

cout << "ASCIIized: ";
cin >> name;
cin.get();
cout << "ASCIIized:\n";
int i = 0;
while (name[i] != '\0')
{
cout << name[i] << ": " << int(name[i]) << endl;
cout << name[i] << ": " << (int *)(char *)(name[i]) << endl;
cout << name[i] << ": " << (int*)(char *)(name[i] + 1) << endl;
i++;
}
cout <<endl<< name[i] << ": " << (int*)(name) << endl;
cout << name[i] << ": " << (int*)(name + 1) << endl;


cin.get();
}

有:

ASCIIized: abc
ASCIIized:
a: 97
a: 00000061
a: 00000062
b: 98
b: 00000062
b: 00000063
c: 99
c: 00000063
c: 00000064

: 0135FD7C
: 0135FD7D
RDF
2017-12-28 08:57:53 +08:00
第二次修订:

// 5.13 print name with while.cpp: 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream>
const int ArSize = 20;
int main()
{
using std::cin;
using std::endl;
using std::cout;
char name[ArSize];

cout << "ASCIIized: ";
cin.getline(name, ArSize);
cout << "ASCIIized:\n";
int i = 0;
while (name[i] != '\0')
{
cout << name[i] << ": " << int(name[i]) << endl;
cout << name[i] << ": " << (int *)(char *)(name[i]) << endl;
cout << name[i] << ": " << (int*)(char *)(name[i] + 1) << endl;
i++;
}
cout <<endl<< name[i] << ": " << (int*)(name) << endl;
cout << name[i] << ": " << (int*)(name + 1) << endl;


cin.get();
}


有:

ASCIIized: abc
ASCIIized:
a: 97
a: 00000061
a: 00000062
b: 98
b: 00000062
b: 00000063
c: 99
c: 00000063
c: 00000064

: 005AFA54
: 005AFA55
RDF
2017-12-28 09:39:19 +08:00
@gnaggnoyil
重新修订并补全说明:
#include "stdafx.h"
#include <iostream>
const int ArSize = 20;
int main()
{
using std::cin;
using std::endl;
using std::cout;
char name[ArSize];

cout << "ASCIIized: ";
cin.getline(name, ArSize);
cout << "ASCIIized:\n";
int i = 0;
while (name[i] != '\0')
{
cout << "打印 ASCII-10 进制-数组第 i 位字符所在地" << endl;
//打印 ASCII-10 进制-数组第 i 位字符所在地
cout << name[i] << ": " << int(name[i]) << endl << endl;

cout << "打印 ASCII-16 进制-数组第 i 位-char 指针-int 指针转" << endl;
//打印 ASCII-16 进制-数组第 i 位-char 指针-int 指针转
cout << name[i] << ": " << (int *)(char *)(name[i]) << endl;
cout << name[i] << ": " << (int*)(char *)(name[i] + 1) << endl << endl;

cout << "打印数组第 i 位字符所在地-内存地址-cout 对 char 内存地址-直接输出其字符" << endl;
//打印数组第 i 位字符所在地-内存地址-cout 对 char 内存地址-直接输出其字符
cout << endl << name[i] << ": " << (&name[i]) << endl;
cout << name[i] << ": " << (&name[i] + 1) << endl << endl;

cout << "打印数组第 i 位字符所在地-内存地址-将 char 内存地址以 int 形式输出" << endl;
//打印数组第 i 位字符所在地-内存地址-将 char 内存地址以 int 形式输出
cout << endl << name[i] << ": " << (int*)(&name[i]) << endl;
cout << name[i] << ": " << (int*)(&name[i] + 1) << endl << endl;

i++;
}
cout << "" << endl;

cout << "打印整个数组的内存地址的开始位置-将 char 类型的地址-转 int 以使其由 cout 正常输出--对 char 类型的内存地址" << endl;
//打印整个数组的内存地址的开始位置-将 char 类型的地址-转 int 以使其由 cout 正常输出--对 char 类型的内存地址
cout <<endl<< name[i] << ": " << (int*)(&name) << endl;
cout << name[i] << ": " << (int*)(&name + 1) << endl << endl;

cout << "打印整个数组的内存地址的开始位置-将 char 类型的地址-对 char 类型的内存地址" << endl;
cout << "解除引用,得整个的 char 内存地址的指针--cout 对 char 指针由开始位置进行输出,直到\\0" << endl;
//打印整个数组的内存地址的开始位置-将 char 类型的地址-对 char 类型的内存地址
//解除引用,得整个的 char 内存地址的指针--cout 对 char 指针由开始位置进行输出,直到\0
cout << endl << name[i] << ": " << (char *)(&name) << endl;
cout << "到达了 char 数组的外部,所以值为未定义" << endl;
//到达了 char 数组的外部,所以值为未定义
cout << name[i] << ": " << (char *)(&name + 1) << endl << endl;

cout << "打印整个数组的内存地址的开始位置-将 char 类型的地址-对 char 类型的内存地址" << endl;
cout << "解除引用,得整个的 char 内存地址的指针-转 int 以使其由 cout 正常输出-直到\\0" << endl;
//打印整个数组的内存地址的开始位置-将 char 类型的地址-对 char 类型的内存地址
//解除引用,得整个的 char 内存地址的指针-转 int 以使其由 cout 正常输出-直到\0
cout << endl << name[i] << ": " << (int*)(char *)(&name) << endl;
cout << "对整个数组内存地址+1-偏移量为整个数组的长度+1" << endl;
cout << "解除引用,得整个偏移后的 char 内存地址的 char 指针--将 char 指针转 int 指针由 cout 正常输出" << endl;
//对整个数组内存地址+1-偏移量为整个数组的长度+1
//解除引用,得整个偏移后的 char 内存地址的 char 指针--将 char 指针转 int 指针由 cout 正常输出
cout << name[i] << ": " << (int*)(char *)(&name + 1) << endl << endl;

cin.get();
}


对应输出:

ASCIIized: abc
ASCIIized:
打印 ASCII-10 进制-数组第 i 位字符所在地
a: 97

打印 ASCII-16 进制-数组第 i 位-char 指针-int 指针转
a: 00000061
a: 00000062

打印数组第 i 位字符所在地-内存地址-cout 对 char 内存地址-直接输出其字符

a: abc
a: bc

打印数组第 i 位字符所在地-内存地址-将 char 内存地址以 int 形式输出

a: 004FFCA4
a: 004FFCA5

打印 ASCII-10 进制-数组第 i 位字符所在地
b: 98

打印 ASCII-16 进制-数组第 i 位-char 指针-int 指针转
b: 00000062
b: 00000063

打印数组第 i 位字符所在地-内存地址-cout 对 char 内存地址-直接输出其字符

b: bc
b: c

打印数组第 i 位字符所在地-内存地址-将 char 内存地址以 int 形式输出

b: 004FFCA5
b: 004FFCA6

打印 ASCII-10 进制-数组第 i 位字符所在地
c: 99

打印 ASCII-16 进制-数组第 i 位-char 指针-int 指针转
c: 00000063
c: 00000064

打印数组第 i 位字符所在地-内存地址-cout 对 char 内存地址-直接输出其字符

c: c
c:

打印数组第 i 位字符所在地-内存地址-将 char 内存地址以 int 形式输出

c: 004FFCA6
c: 004FFCA7


打印整个数组的内存地址的开始位置-将 char 类型的地址-转 int 以使其由 cout 正常输出--对 char 类型的内存地址

: 004FFCA4
: 004FFCB8

打印整个数组的内存地址的开始位置-将 char 类型的地址-对 char 类型的内存地址
解除引用,得整个的 char 内存地址的指针--cout 对 char 指针由开始位置进行输出,直到\0

: abc
到达了 char 数组的外部,所以值为未定义
: 烫烫 4栽麿

打印整个数组的内存地址的开始位置-将 char 类型的地址-对 char 类型的内存地址
解除引用,得整个的 char 内存地址的指针-转 int 以使其由 cout 正常输出-直到\0

: 004FFCA4
对整个数组内存地址+1-偏移量为整个数组的长度+1
解除引用,得整个偏移后的 char 内存地址的 char 指针--将 char 指针转 int 指针由 cout 正常输出
: 004FFCB8



应该说明完善了。
RDF
2017-12-28 09:52:08 +08:00
补充:

为了测试阈限,
const int ArSize = 2;
输入为 a




字符串存的字符分别为‘ a ’和‘\0 ’

可以检查得更为仔细:


ASCIIized: a
ASCIIized:
打印 ASCII-10 进制-数组第 i 位字符所在地
a: 97

打印 ASCII-16 进制-数组第 i 位-char 指针-int 指针转
a: 00000061
a: 00000062

打印数组第 i 位字符所在地-内存地址-cout 对 char 内存地址-直接输出其字符

a: a
a:

打印数组第 i 位字符所在地-内存地址-将 char 内存地址以 int 形式输出

a: 0113F998
a: 0113F999


打印整个数组的内存地址的开始位置-将 char 类型的地址-转 int 以使其由 cout 正常输出--对 char 类型的内存地址

: 0113F998
: 0113F99A

打印整个数组的内存地址的开始位置-将 char 类型的地址-对 char 类型的内存地址
解除引用,得整个的 char 内存地址的指针--cout 对 char 指针由开始位置进行输出,直到\0

: a
到达了 char 数组的外部,所以值为未定义
: 烫烫烫坉 X N;

打印整个数组的内存地址的开始位置-将 char 类型的地址-对 char 类型的内存地址
解除引用,得整个的 char 内存地址的指针-转 int 以使其由 cout 正常输出-直到\0

: 0113F998
对整个数组内存地址+1-偏移量为整个数组的长度+1
解除引用,得整个偏移后的 char 内存地址的 char 指针--将 char 指针转 int 指针由 cout 正常输出
: 0113F99A
RDF
2017-12-28 09:56:51 +08:00
这样细节就拦清了 :)

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/417604

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX