Via*_*iuk 1 c concatenation null-terminated
我想连接两个定义如下的字符串:
char hello[] = { 'H', 'e', 'l', 'l', 'o', '\0' };
char world[] = { ',', ' ', 'W', 'o', 'r', 'l', 'd', '!', '\0' };
Run Code Online (Sandbox Code Playgroud)
我明白我应该跑过第一个,找到'\0'标志而不是它开始第二个字符串.功能是否strcat以相同的方式工作?
我正在使用的代码:
for (int i = 0; i < 6; i++) {
if (hello[i] == '\0') {
for (int j = 0; j < 9; j++) {
int index = 5 + j;
hello[index] = world[j];
}
}
}
Run Code Online (Sandbox Code Playgroud)
编译后我得到这样一个错误:
*堆栈粉碎检测到*:./ run终止
我究竟做错了什么?
Ahm*_*sud 13
我的回答最初不会集中在正确连接字符串上; 而是试图解决你的代码中的一些问题,并给你一些背景思路,可能有助于澄清如何思考C中的事情.然后我们将看看连接字符串
用C思考非常像计算机(CPU,内存等); 因此,对于在CPU上本地工作的数据类型,C具有字符(单字节事物),短路(双字节字),长字(4字节字),整数,浮点数和双精度数,这些都是CPU本身可以理解的.并且能够创建这些东西的数组或指向存在这些类型的内存位置的指针.
那么我们如何创建一个字符串呢?我们创建一个新类型吗?
好吧,既然CPU不理解字符串,那么C ...也不是最原始的形式(C语法分析器没有与字符串相关联的类型).
但字符串是非常有用的,所以必须有一个相当简单的概念,即字符串应该由它决定.
所有C字符串都是顺序存储器中不包含NUL字符的字节;
NUL(发音类似于nool)是我们给内存中值为0的字节赋值的名称.在C中,这表示为\0.所以如果我写NUL就意味着性格\0;
注1:这与C NULL不同,C NULL是零值的存储器地址 ;
注2:NUL当然不是字符零('0'),其值为48;
因此,对字符串起作用的任何函数都会启动char*指向的内存位置(读取char指针); 并继续按字节(字符)继续执行其操作字节(字符),直到它为表示字符串结尾的字节的值为0.在那个时候,希望它停止做它正在做的事情,因为字符串已经结束并返回其操作的结果.
因此,如果我们将字符串定义为以0结尾的字符数组,并且我们完全避免必须创建除此之外的任何人为的字符串概念.
而这正是C的作用; 它只是将这个概念作为惯例来解决; 并且编译器只提供了一个简单的快捷方式来声明使用双引号终止NUL的字符数组,就是这样.C语言中没有特殊的字符串类型.
所以考虑到所有这些,让我们看看你的代码:
char hello[] = { 'H', 'e', 'l', 'l', 'o', '\0' };
char world[] = { ',', ' ', 'W', 'o', 'r', 'l', 'd', '!', '\0' };
Run Code Online (Sandbox Code Playgroud)
您声明了两个单字节数组(char)并用\ 0终止它们; 这对以下C语句是IDENTICAL:
char hello[] = "Hello";
char world[] = ", World!";
Run Code Online (Sandbox Code Playgroud)
在64位英特尔计算机上运行的Linux计算机上编译时,您的配对和上面的配对都会发出以下(相同的)机器代码输出:
Disassembly of section .data:
0000000000000000 <hello>:
0: 48 65 6c 6c 6f 00 Hello.
0000000000000006 <world>:
6: 2c 20 57 6f 72 6c 64 21 00 , World!.
Run Code Online (Sandbox Code Playgroud)
如果你使用的是Linux,你可以尝试一下; 让我知道,我将在下面向您展示这些命令作为附录.
请注意,在这两种情况下00,最后都会出现一个字节.在你的情况下,你在数组中明确声明了它; 在第二种情况下,当发出对应于<hello>和<world>符号的数据时,它由C编译器隐式注入.
好的,现在您已了解其运作方式; 你可以看到:
// This is bad: :-)
for (int i = 0; i < 6; i++) {
if (hello[i] == '\0') {
for (int j = 0; j < 9; j++) {
int index = 5 + j;
hello[index] = world[j];
}
}
}
Run Code Online (Sandbox Code Playgroud)
上面的循环非常奇怪.实际上它有一堆错误(例如嵌套在外for循环中的循环是错误的);
但是,不要指出问题,让我们看看基本的正确解决方案.
当你为字符串编程时,你不知道它们有多大; 这样形式的条件i < N在for处理字符串的循环是不平常的路要走.
这是一种循环字符串中的字符的方法(以终止的字符数组\0):
char *p; /* Points to the characters in strings */
char str[] = "Hello";
for ( p = str; *p != 0; p++ ) {
printf("%c\n", *p);
}
Run Code Online (Sandbox Code Playgroud)
那么让我们弄清楚这里发生了什么:
for ( p = str; ...
^^^^^^^^^
Run Code Online (Sandbox Code Playgroud)
p是一个char指针.在开始时我们指向它hello(这是运行程序时变量hello在内存中加载的位置)并检查此内存位置的值(获取*p)是否等于'\ 0':
for (p = str; *p != 0; ...)
^^^^^^^
Run Code Online (Sandbox Code Playgroud)
如果不是我们for循环,因为条件是真的; 在我们的例子中*p=='H',我们进入循环:
for (p = str; *p != 0; p++)
^^^
Run Code Online (Sandbox Code Playgroud)
现在我们先做增量/减量/其他事情.但在这种情况下,++操作员被固定到p; so p(这是一个内存地址)将在循环语句的END处增加其值; 所以现在循环进入了{ ... }它做的事情,最后++发生了,我们再次进入条件检查:
for (p = str; *p != 0; p++)
^^^^^^^
Run Code Online (Sandbox Code Playgroud)
所以你可以看到这将p指向'H''e''l''''o''\ 0'的内存位置; 然后命中'\ 0'它将退出.
所以现在我们知道我们想要连接"Hello"和",World!".
首先,我们需要找到结束,Hello然后我们需要开始坚持",世界!" 到最后:
我们知道for上面的循环找到你好的结尾; 所以如果我们在它的末尾没有做任何事情,那么*p将指向结尾的'\ 0' Hello:
char str1[] = "Hello";
char str2[] = ", World";
char *p; /* points str1 */
char *q; /* points str2 */
for (p = str1; *p!=0; p++) {
/* Skip along till the end */
}
/* Here p points to '\0' in str1 */
/* Now we start to copy characters from str2 to str1 */
for (q = str2; *q != 0; p++, q++ ) {
*p = *q;
}
Run Code Online (Sandbox Code Playgroud)
请注意,在第一遍*p中指向str1末尾的'\ 0',所以当我们指定*p = *q'\ 0'被','替换时; 并且'\ 0'完全从str1中消失,我们必须在最后注入; 请注意,我们仍然必须增加p并q在结束时继续循环*q != 0.
现在循环结束了,我们在末尾粘贴'\ 0',因为我们销毁了我们拥有的那个:
*p = 0;
Run Code Online (Sandbox Code Playgroud)
这就是连接.
如果你注意到上面的汇编输出; Hello\0占用了六个字节并, World\0从0000000006数据段中的地址(hello start at 000000000)开始.
这意味着如果你写的超出str1 []的字节数并且它没有足够的空间就是我们的情况(为什么在下面解释),我们最终会覆盖属于其他东西的部分内存(str2) []) 例如;
我们没有足够内存的原因是因为我们刚刚声明了一个足以保存初始化值的字符数组:
char str[] = "Foofoo";
Run Code Online (Sandbox Code Playgroud)
将str设为正好7个字节.
但我们可以要求C提供更多的空间str而不仅仅是初始化值.例如,
char str[20] = "Foofoo";
Run Code Online (Sandbox Code Playgroud)
这将给出str20个字节,并将前七个设置为"Foofoo\0".其余的通常\0也是如此;
所以上面的拆解看起来像:
Disassembly of section .data:
0000000000000000 <str>:
0: 48 65 6c 6c 6f 00 00 00 00 00 00 00 00 00 00 00 Foofoo..........
10: 00 00 00 00 ....
Run Code Online (Sandbox Code Playgroud)
记住在C中你必须像电脑一样思考.如果你没有明确要求记忆,你将无法获得记忆.因此,如果我们要进行连接,我们必须使用足够大的数组,因为我们以这种方式显式声明:
char foo[1000]; /* Lots of room */
Run Code Online (Sandbox Code Playgroud)
或者我们在运行时使用malloc(另一篇文章的主题)请求内存位置.
让我们看看一个有效的解决方案:
#include <stdio.h>
char str1[100] = "Hello";
char str2[] = ", World!"; /* No need to make this big */
int main()
{
char *p;
char *q;
printf("str1 (before concat): %s\n", str1);
for (p = str1; *p != 0; p++) {
/* Skip along to find the end */
}
for (q = str2; *q != 0; p++, q++ ) {
*p = *q;
}
*p = 0; /* Set the last character to 0 */
printf("str1 (after concat): %s\n", str1);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
如果你将上面的内容编译成JUST一个目标文件并且不将它链接到一个可执行文件,你就会把事情弄得乱七八糟:
gcc -c concat.c -o concat.o
Run Code Online (Sandbox Code Playgroud)
您可以使用对象转储反汇编concat.o:
objdump -d concat.o
Run Code Online (Sandbox Code Playgroud)
您会注意到转储中有很多不必要的代码处理printf语句:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 83 ec 10 sub $0x10,%rsp
8: be 00 00 00 00 mov $0x0,%esi
d: bf 00 00 00 00 mov $0x0,%edi
12: b8 00 00 00 00 mov $0x0,%eax
17: e8 00 00 00 00 callq 1c <main+0x1c>
Run Code Online (Sandbox Code Playgroud)
所以要摆脱它,只需在代码中注释掉printf.然后使用该行重新编译
gcc -O3 -c concat.c -o concat.o
Run Code Online (Sandbox Code Playgroud)
再次.现在你将获得更清洁的输出;
在-O3去除了一些帧指针(晚得多主题)相关指令和汇编程序将具体到你的代码库:
这是使用上面编译时的concat.o输出,并使用以下方法转出:
objdump -S -s concat.o
concat.o: File format elf64-x86-64
Contents of section .text:
0000 803d0000 000000b8 00000000 740b6690 .=..........t.f.
0010 4883c001 80380075 f70fb615 00000000 H....8.u........
0020 84d2741d b9000000 000f1f80 00000000 ..t.............
0030 4883c101 88104883 c0010fb6 1184d275 H.....H........u
0040 efc60000 31c0c3 ....1..
Contents of section .data:
0000 48656c6c 6f000000 00000000 00000000 Hello...........
0010 00000000 00000000 00000000 00000000 ................
0020 00000000 00000000 00000000 00000000 ................
0030 00000000 00000000 00000000 00000000 ................
0040 00000000 00000000 00000000 00000000 ................
0050 00000000 00000000 00000000 00000000 ................
0060 00000000 2c20576f 726c6421 00 ...., World!.
Contents of section .comment:
0000 00474343 3a202844 65626961 6e20342e .GCC: (Debian 4.
0010 342e352d 38292034 2e342e35 00 4.5-8) 4.4.5.
Contents of section .eh_frame:
0000 14000000 00000000 017a5200 01781001 .........zR..x..
0010 1b0c0708 90010000 14000000 1c000000 ................
0020 00000000 47000000 00000000 00000000 ....G...........
Disassembly of section .text:
0000000000000000 <main>:
0: 80 3d 00 00 00 00 00 cmpb $0x0,0x0(%rip) # 7 <main+0x7>
7: b8 00 00 00 00 mov $0x0,%eax
c: 74 0b je 19 <main+0x19>
e: 66 90 xchg %ax,%ax
10: 48 83 c0 01 add $0x1,%rax
14: 80 38 00 cmpb $0x0,(%rax)
17: 75 f7 jne 10 <main+0x10>
19: 0f b6 15 00 00 00 00 movzbl 0x0(%rip),%edx # 20 <main+0x20>
20: 84 d2 test %dl,%dl
22: 74 1d je 41 <main+0x41>
24: b9 00 00 00 00 mov $0x0,%ecx
29: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
30: 48 83 c1 01 add $0x1,%rcx
34: 88 10 mov %dl,(%rax)
36: 48 83 c0 01 add $0x1,%rax
3a: 0f b6 11 movzbl (%rcx),%edx
3d: 84 d2 test %dl,%dl
3f: 75 ef jne 30 <main+0x30>
41: c6 00 00 movb $0x0,(%rax)
44: 31 c0 xor %eax,%eax
46: c3 retq
Run Code Online (Sandbox Code Playgroud)