在数组的最后一个元素之后将 '\0' 放入 char[] 是否安全?
Is it safe to put '\0' to char[] one after the last element of the array?
出于一些实际原因,我很感兴趣。我知道 C++ 在最后一个元素之后添加 '[=11=]'
,但是手动放置它安全吗?我听说过 undefined behavior
,但是我很想知道 NULL
字符是否真的是内存中的下一个符号?
UPD:我明白了,没有代码片段我的问题不够清楚。
所以,我问这是否真的保存并且不会导致未定义的行为?
const char* a = "Hello";
a[5] = '[=10=]'; //this is wrong, will not compile on most compilers
char* b = new char[5];
b[5] = '[=10=]';
char c[5];
c[5] = '[=10=]';
您似乎混淆了一些细节。
首先,看一下这段代码(C++)的输出:
#include <iostream>
int main()
{
char x[] = {'a','b','c'};
std::cout << sizeof(x);
}
它打印 3
,因为 x
是一个包含 3 个元素的数组。此处不会自动添加任何内容。尝试在 x[3]
处“添加”空终止符是不可能的,因为该数组只有 3 个元素(最后一个有效索引是 2
)。尝试访问 x[3]
是未定义的行为。
我想你指的是 string literals。他们确实附加了 [=20=]
:
The null character ('[=33=]', L'[=33=]', char16_t(), etc) is always appended
to the string literal: thus, a string literal "Hello" is a const
char[6] holding the characters 'H', 'e', 'l', 'l', 'o', and '[=33=]'.
但是,这并不意味着您不能在字符串文字中插入空终止符。它只是不太实用,因为它破坏了空终止符终止字符串 (*):
的目的
#include <iostream>
int main()
{
const char* x = "H[=11=]ello";
std::cout << x << "\n";
for (int i=0;i<7; ++i) std::cout << x[i];
}
H
Hello
(空终止符不可打印)。
(*) 或者更确切地说:它是第一个终止字符串的 [=20=]
。任何需要空终止字符串的函数都将在遇到第一个 [=20=]
时停止。
关于问题的更新....
const char* a = "Hello";
a[5] = '[=13=]';
字符串文字 "Hello"
的类型为 const char[6]
。它已经有空终止符 space 并且 a[5]
已经等于 [=20=]
。您不能将 [=20=]
分配给最后一个元素,因为字符串文字是常量,您无法修改它们。实际上该行甚至无法编译,因为 a
是 const char*
.
另外两个
char* b = new char[5];
b[5] = '[=14=]';
char c[5];
c[5] = '[=14=]';
属于未定义行为,因为你正在越界访问数组。具有 5 个元素的数组中的最后一个有效索引是 4。
更新后很明显你的误会是空终止符是在什么时候添加的。这不是 char
数组的特性,但是当你写 "foo"
时,它的类型是 const char[4]
,已经有足够的 space 用于字符串 and 空终止符。
I know C++ adds '[=15=]' after the last element
这不是真的;至少缺少上下文,或者最坏的情况是误解。 字符串文字 末尾有一个隐式空终止符。字符串文字的隐式空终止符是数组的一部分。虽然它是字符串的最后一个字符 after,但它不是数组的最后一个元素 after;终止符 是 数组的最后一个元素。例如,字符串文字 ""
是一个包含一个元素(空终止符)的数组,而字符串文字 "a"
是一个包含两个元素('a' 和空终止符)的数组。 "[=14=]"
是一个包含两个元素(两个空终止符)的数组。
除字符串文字外,没有其他数组具有隐式空终止符。
Is it safe to put '[=16=]' to char[] one after the last element of the array?
在数组边界之外读取或写入内存的行为未定义。它与安全相反。
const char* a = "Hello";
a[5] = '[=10=]';
不允许通过指向 const 的指针进行赋值。这个程序是病式的,如果编译器拒绝接受这个程序是安全的。
char* b = new char[5];
b[5] = '[=11=]';
char c[5];
c[5] = '[=11=]';
索引 5 超出数组范围。行为未定义。很不安全。
在 C++ 中,如果声明一个由字符串文字初始化的字符数组,并且明确指定数组中元素的数量,则还需要考虑隐式存在于中的终止零字符“\0”字符串文字。例如
char s[6] = "Hello";
^^^
这个声明等同于
char s[6] = { 'H', 'e', 'l', 'l', 'o', '[=11=]' };
在 C 语言中,声明字符数组时可以忽略字符串文字的终止零字符。例如
char s[5] = "Hello";
^^^
这个声明等同于
char s[5] = { 'H', 'e', 'l', 'l', 'o' };
但在这种情况下,这样的字符数组不包含字符串。因此,例如在函数中使用这样的数组就像
puts( s ):
或者在大多数标准字符串函数中调用未定义的行为。
当然,如果您想通过字符串文字初始化数组并保证数组将包含字符串,则可以在不明确指定数组中元素数量的情况下声明数组。
char s[] = "Hello";
这个声明等同于
char s[] = { 'H', 'e', 'l', 'l', 'o', '[=16=]' };
并且声明的数组将恰好包含 6 个元素。
编辑: 在您将问题附加到此代码段后
const char* a = "Hello";
a[5] = '[=17=]';
char* b = new char[5];
b[5] = '[=17=]';
char c[5];
c[5] = '[=17=]';
那么你不能像这样使用指向对象的常量指针来更改对象
const char* a = "Hello";
a[5] = '[=18=]';
此外,如果您要删除限定符 const
(在 C 中,字符串文字具有与 C++ 相反的非常量字符数组类型)
char* a = "Hello";
但是您不能更改字符串文字。尝试更改 C 和 C++ 中的字符串文字会导致未定义的行为。
在这部分代码片段中
char* b = new char[5];
b[5] = '[=20=]';
char c[5];
c[5] = '[=20=]';
您正在尝试访问超出分配数组的内存,因为索引的有效范围是 [0, 4]
,这再次调用未定义的行为。
你可以这样写
char* b = new char[5]();
char c[5] = {};
在这种情况下,数组将被零初始化。
或者你可以这样写
char* b = new char[5];
b[0] = '[=22=]'; // that is the same as *b = '[=22=]';
char c[5];
c[0] = '[=22=]'; // that is the same as *c = '[=22=]';
在这两种情况下,数组都将包含空字符串。
片段
const char* a = "Hello";
a[5] = '[=10=]';
甚至不编译;不是因为索引 5 超出范围,而是因为 a
被声明为指向常量内存。 “指向常量内存的指针”的意思是“我声明我不想写它”,所以语言和编译器都禁止它。
注意const
的主要作用是声明程序员的意图。实际上,您是否可以写入它取决于。在您的示例中,尝试 - 在 const 转换之后 - 会使您的程序崩溃,因为现代编译器将字符文字放入只读内存中。
但请考虑:
#include <iostream>
using namespace std;
int main()
{
// Writable memory. Initialized with zeroes (interpreted as a string it is empty).
char writable[2] = {};
// I_swear_I_wont_write_here points to writable memory
// but I solemnly declare not to write through it.
const char* I_swear_I_wont_write_here = writable;
cout << "I_swear_I_wont_write_here: ->" << I_swear_I_wont_write_here << "<-\n";
// I_swear_I_wont_write_here[1] = 'A'; // <-- Does not compile. I'm bound by the oath I took.
// Screw yesterday's oaths and give me an A.
// This is well defined and works. (It works because the memory
// is actually writable.)
const_cast<char*>(I_swear_I_wont_write_here)[0] = 'A';
cout << "I_swear_I_wont_write_here: ->" << I_swear_I_wont_write_here << "<-\n";
}
声明一些东西 const
只是宣布你不想通过它来写;这并不意味着相关内存确实是不可写的,程序员可以自由地忽略声明,但必须通过强制转换明确地这样做。反之亦然,但不需要强制转换:欢迎您声明并坚持“此处无意写作”,不会造成任何伤害。
出于一些实际原因,我很感兴趣。我知道 C++ 在最后一个元素之后添加 '[=11=]'
,但是手动放置它安全吗?我听说过 undefined behavior
,但是我很想知道 NULL
字符是否真的是内存中的下一个符号?
UPD:我明白了,没有代码片段我的问题不够清楚。 所以,我问这是否真的保存并且不会导致未定义的行为?
const char* a = "Hello";
a[5] = '[=10=]'; //this is wrong, will not compile on most compilers
char* b = new char[5];
b[5] = '[=10=]';
char c[5];
c[5] = '[=10=]';
您似乎混淆了一些细节。
首先,看一下这段代码(C++)的输出:
#include <iostream>
int main()
{
char x[] = {'a','b','c'};
std::cout << sizeof(x);
}
它打印 3
,因为 x
是一个包含 3 个元素的数组。此处不会自动添加任何内容。尝试在 x[3]
处“添加”空终止符是不可能的,因为该数组只有 3 个元素(最后一个有效索引是 2
)。尝试访问 x[3]
是未定义的行为。
我想你指的是 string literals。他们确实附加了 [=20=]
:
The null character ('[=33=]', L'[=33=]', char16_t(), etc) is always appended to the string literal: thus, a string literal "Hello" is a const char[6] holding the characters 'H', 'e', 'l', 'l', 'o', and '[=33=]'.
但是,这并不意味着您不能在字符串文字中插入空终止符。它只是不太实用,因为它破坏了空终止符终止字符串 (*):
的目的#include <iostream>
int main()
{
const char* x = "H[=11=]ello";
std::cout << x << "\n";
for (int i=0;i<7; ++i) std::cout << x[i];
}
H
Hello
(空终止符不可打印)。
(*) 或者更确切地说:它是第一个终止字符串的 [=20=]
。任何需要空终止字符串的函数都将在遇到第一个 [=20=]
时停止。
关于问题的更新....
const char* a = "Hello";
a[5] = '[=13=]';
字符串文字 "Hello"
的类型为 const char[6]
。它已经有空终止符 space 并且 a[5]
已经等于 [=20=]
。您不能将 [=20=]
分配给最后一个元素,因为字符串文字是常量,您无法修改它们。实际上该行甚至无法编译,因为 a
是 const char*
.
另外两个
char* b = new char[5];
b[5] = '[=14=]';
char c[5];
c[5] = '[=14=]';
属于未定义行为,因为你正在越界访问数组。具有 5 个元素的数组中的最后一个有效索引是 4。
更新后很明显你的误会是空终止符是在什么时候添加的。这不是 char
数组的特性,但是当你写 "foo"
时,它的类型是 const char[4]
,已经有足够的 space 用于字符串 and 空终止符。
I know C++ adds '[=15=]' after the last element
这不是真的;至少缺少上下文,或者最坏的情况是误解。 字符串文字 末尾有一个隐式空终止符。字符串文字的隐式空终止符是数组的一部分。虽然它是字符串的最后一个字符 after,但它不是数组的最后一个元素 after;终止符 是 数组的最后一个元素。例如,字符串文字 ""
是一个包含一个元素(空终止符)的数组,而字符串文字 "a"
是一个包含两个元素('a' 和空终止符)的数组。 "[=14=]"
是一个包含两个元素(两个空终止符)的数组。
除字符串文字外,没有其他数组具有隐式空终止符。
Is it safe to put '[=16=]' to char[] one after the last element of the array?
在数组边界之外读取或写入内存的行为未定义。它与安全相反。
const char* a = "Hello"; a[5] = '[=10=]';
不允许通过指向 const 的指针进行赋值。这个程序是病式的,如果编译器拒绝接受这个程序是安全的。
char* b = new char[5]; b[5] = '[=11=]'; char c[5]; c[5] = '[=11=]';
索引 5 超出数组范围。行为未定义。很不安全。
在 C++ 中,如果声明一个由字符串文字初始化的字符数组,并且明确指定数组中元素的数量,则还需要考虑隐式存在于中的终止零字符“\0”字符串文字。例如
char s[6] = "Hello";
^^^
这个声明等同于
char s[6] = { 'H', 'e', 'l', 'l', 'o', '[=11=]' };
在 C 语言中,声明字符数组时可以忽略字符串文字的终止零字符。例如
char s[5] = "Hello";
^^^
这个声明等同于
char s[5] = { 'H', 'e', 'l', 'l', 'o' };
但在这种情况下,这样的字符数组不包含字符串。因此,例如在函数中使用这样的数组就像
puts( s ):
或者在大多数标准字符串函数中调用未定义的行为。
当然,如果您想通过字符串文字初始化数组并保证数组将包含字符串,则可以在不明确指定数组中元素数量的情况下声明数组。
char s[] = "Hello";
这个声明等同于
char s[] = { 'H', 'e', 'l', 'l', 'o', '[=16=]' };
并且声明的数组将恰好包含 6 个元素。
编辑: 在您将问题附加到此代码段后
const char* a = "Hello";
a[5] = '[=17=]';
char* b = new char[5];
b[5] = '[=17=]';
char c[5];
c[5] = '[=17=]';
那么你不能像这样使用指向对象的常量指针来更改对象
const char* a = "Hello";
a[5] = '[=18=]';
此外,如果您要删除限定符 const
(在 C 中,字符串文字具有与 C++ 相反的非常量字符数组类型)
char* a = "Hello";
但是您不能更改字符串文字。尝试更改 C 和 C++ 中的字符串文字会导致未定义的行为。
在这部分代码片段中
char* b = new char[5];
b[5] = '[=20=]';
char c[5];
c[5] = '[=20=]';
您正在尝试访问超出分配数组的内存,因为索引的有效范围是 [0, 4]
,这再次调用未定义的行为。
你可以这样写
char* b = new char[5]();
char c[5] = {};
在这种情况下,数组将被零初始化。
或者你可以这样写
char* b = new char[5];
b[0] = '[=22=]'; // that is the same as *b = '[=22=]';
char c[5];
c[0] = '[=22=]'; // that is the same as *c = '[=22=]';
在这两种情况下,数组都将包含空字符串。
片段
const char* a = "Hello";
a[5] = '[=10=]';
甚至不编译;不是因为索引 5 超出范围,而是因为 a
被声明为指向常量内存。 “指向常量内存的指针”的意思是“我声明我不想写它”,所以语言和编译器都禁止它。
注意const
的主要作用是声明程序员的意图。实际上,您是否可以写入它取决于。在您的示例中,尝试 - 在 const 转换之后 - 会使您的程序崩溃,因为现代编译器将字符文字放入只读内存中。
但请考虑:
#include <iostream>
using namespace std;
int main()
{
// Writable memory. Initialized with zeroes (interpreted as a string it is empty).
char writable[2] = {};
// I_swear_I_wont_write_here points to writable memory
// but I solemnly declare not to write through it.
const char* I_swear_I_wont_write_here = writable;
cout << "I_swear_I_wont_write_here: ->" << I_swear_I_wont_write_here << "<-\n";
// I_swear_I_wont_write_here[1] = 'A'; // <-- Does not compile. I'm bound by the oath I took.
// Screw yesterday's oaths and give me an A.
// This is well defined and works. (It works because the memory
// is actually writable.)
const_cast<char*>(I_swear_I_wont_write_here)[0] = 'A';
cout << "I_swear_I_wont_write_here: ->" << I_swear_I_wont_write_here << "<-\n";
}
声明一些东西 const
只是宣布你不想通过它来写;这并不意味着相关内存确实是不可写的,程序员可以自由地忽略声明,但必须通过强制转换明确地这样做。反之亦然,但不需要强制转换:欢迎您声明并坚持“此处无意写作”,不会造成任何伤害。