如何解决不同操作系统/编译器之间的C样式字符串输出不一致
我有一个C ++程序:
#include <iostream>
char * foo (char * bar,const char * baz) {
int i = -1;
do {
i++;
*(bar + i) = *(baz + i);
} while (*(baz + i));
return bar;
}
int main (int argc,char *argv[]) {
char bar[] = "";
char baz[] = "Hello";
foo(bar,baz);
std::cout << "bar: " << bar << std::endl;
std::cout << "baz: " << baz << std::endl;
}
并非这是重要的部分,但是此程序的要求是它使用指针将一个C style string复制到另一个{3>}。
当我在Ubuntu 16.04桌面上编译并执行二进制文件时,会看到以下信息:
$ g++ -std=c++11 test.cpp -o test && ./test
bar: Hello
baz: ello
爱! 'H'
的开头baz
已删除,但我完全看不到foo
函数如何更改baz
。嗯...
因此,我的Ubuntu桌面上的g ++版本是:
$ g++ --version
g++ (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609
Copyright (C) 2015 Free Software Foundation,Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
我以为这是我的代码中的错误或错误(并且可能仍然是),但是我发现当我在任何其他操作系统上编译并运行时,都会得到不同的行为。
这是macOS上的输出:
$ g++ -std=c++11 test.cpp -o test && ./test
bar: Hello
baz: Hello
这是该macOS笔记本电脑上的g ++版本:
$ g++ --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/4.2.1
Apple clang version 12.0.0 (clang-1200.0.32.2)
Target: x86_64-apple-darwin19.5.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
在其他Linux机器,Windows等设备上进行测试时,其预期的bar
和baz
的正确输出都是Hello
。
这是怎么回事!?
tl; dr C ++程序在我的台式机上输出的C样式字符串与其他任何计算机都不相同。为什么?
解决方法
char bar[] = "";
这样可以保证创建一个内存区域,该内存区域的长度为一个字节(基本上刚好足以容纳'\0'
)。 实现可以为您提供更多,但您不能依靠它。
因此,它不足以存储字符串"Hello"
,该字符串需要六个字节。例如,C++20 [expr.add]
对此进行了介绍,我特别强调:
如果表达式
P
指向具有x[i]
个元素的数组对象x
的元素n
,则表达式P + J
和J + P
(如果J
,则j
的值为x[i + j]
)指向(可能是假设的)元素0 <= i + j <= n
; 否则,行为是不确定的。
如果要确保此代码段中有足够的空间,只需将声明更改为:
char baz[] = "Hello";
char bar[sizeof(baz)]; // bar will be same size as baz
对于其他情况,有不同的方法来保证此大小,但是一般规则仍然相同:确保目标数组足够大,以免超出其末尾。
尽管未定义的行为表示可能发生任何事情,但在您的错误情况下最可能发生的事情是与以下堆栈上的内存布局有关。您将字符从baz
到bar
一一复制(其中$
代表\0
字符),从而产生以下快照之前和之后:
bar
V
+---+---+---+---+---+---+---+
| $ | H | e | l | l | o | $ | (before)
+---+---+---+---+---+---+---+
| H | e | l | l | o | $ | $ | (after)
+---+---+---+---+---+---+---+
^
baz
因此,您可以看到超出bar
末尾的写入如何影响堆栈上的其他事物,例如baz
。如果堆栈布局不同,则效果很可能也会不同。
例如,如果bar
和baz
以 other 的顺序排列在堆栈上,则bar
将不影响baz
。几乎肯定会影响堆栈上的其他内容,从而导致异常行为,特别是如果其他内容恰好是调用函数的返回地址:-)
最重要的是,未定义的行为确实意味着-您不能依赖任何可以正常工作的东西。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。