如果我有一个嵌入的空终止符[旁边:是那个UB吗?],我是否可以很好地定义它后面的值?
#include <stdio.h>
const char foo[] = "abc\0def";
int main() {
printf("%s", foo+4);
return sizeof(foo);
}
对于记录,它打印出您可能期望的内容:
def
答案 0 :(得分:3)
嵌入式null
不是未定义的行为。如果您使用期望字符串为空终止的函数,那么可能是一个逻辑错误。但是,无论其内容如何,访问您已成功分配的数组的全部宽度都没有错或恶或未定义。
要注意的一件事是:如果您尝试将此数据存储在std::string
中(这是您应该如何处理所有字符串,TBH), 存储字符串的方式可以是重要的。
std::string str1 = foo; //contents of str1 becomes "abc".
std::string str2 = std::string(foo, sizeof(foo)); //contents of str2 becomes "abc\0def"
答案 1 :(得分:2)
[dcl.init.string]陈述
窄字符类型(3.9.1),char16_t数组,char32_t数组或wchar_t数组的数组可以分别由窄字符串文字,char16_t字符串文字,char32_t字符串文字或宽字符串文字初始化,或者由括号中的适当类型的字符串文字(2.14.5)。 字符串文字值的连续字符初始化数组的元素。
强调我的
因此嵌入式null不是问题,它只是成为数组的一个元素。由于数组的大小可以容纳所有字符和转义序列,因此我们知道在嵌入null之后存在元素,并且访问它们是安全的。
嵌入式null的唯一问题是任何C函数在遇到null时都会停止,并且不会完全处理字符串。您可以考虑使用std::string
而不是那些问题。
答案 2 :(得分:2)
访问一个C字符串beyound终止空字符本身从不是未定义的行为。尽管如此,我们可以以这种方式产生未定义的行为,但原因完全不同:
如果终止空字符恰好位于为字符串保留的char数组的最后一个位置,那么如果我们访问超出其结尾的字符串,我们就会从其边界中访问此底层数组。而这种超出范围的访问真正产生了未定义的行为......
编辑:
[旁边:那是UB吗?]
UB,未定义的行为,是无法定义的行为,因为没有有意义的行为。依赖于未定义的行为可以导致任何事情,包括获得预期的结果,但是在任何其他时间都可能失败(例如在另一个平台上,在切换编译器版本之后,在简单地重新编译之后,甚至在重新启动一个和相同的程序之后)。因此,依赖于未定义行为的程序被认为没有很好地定义。
示例:取消引用指向已删除对象的指针(“悬空指针”),或者接近问题:访问数组越界(可能导致尝试访问不符合当前状态的内存)进程甚至不存在,但可以读取或( bad !!! )覆盖恰好位于给定地址的完全不同的对象的内存(它甚至不必是同一个对象每次你的程序运行,甚至在一个程序运行中都没有。)
未定义的行为不会与未指定的行为混淆(或同义,实现定义的行为):在这种情况下,给定输入的行为已明确定义,但由编译器供应商定义其中的行为一些给出了合理的限制。
示例:负整数的右移 - 可以在有或没有符号扩展的情况下发生(因此可以是算术或逻辑移位)。但是,标准没有规定哪一个适用,但在负整数上使用右移是很明确的。