不使用strcmp()进行字符串比较

4

strcmp函数比较字符串的内容,因此它比比较字符串基地址的if (str1 == str2)更可靠。

那么,为什么下面的C代码中if条件会被满足呢:

    char *p2="sample1";
    char* str[2]={"sample1","sample2"};


    if(p2==str[0])
    {
            printf("if condition satisfied\n");
    }

GDB:

(gdb) p p2
$1 = 0x4005f8 "sample1"
(gdb) p str[0]
$2 = 0x4005f8 "sample1"
(gdb) p &p2
$3 = (char **) 0x7fffffffdb38
(gdb) p &str[0]
$4 = (char **) 0x7fffffffdb20
(gdb) p *p2
$5 = 115 's'

什么是0x4005f8,如何将其打印出来?


编译器正在优化并仅存储sample1的一个实例。 - kiran Biradar
"sample1"和"sample2"是字符串常量,不允许更改。编译器可以自由地识别重复的字符串常量并为它们使用相同的内存,从而减少总内存使用量。 - Tom Karzes
可能是为什么这段代码的行为在C中未定义?的重复问题。 - alinsoar
4个回答

5

无论相同的字符串字面值是否会分配不同的存储空间,或者相同的存储空间将用于表示字符串字面值的所有使用,都是未指定的。

在您的情况下,字符串字面值"Sample1"只有一个副本,并且相同的地址分配给p2str[0]。然而,这不被标准保证。

引用C11,第6.4.5章

未指定这些数组是否不同,只要它们的元素具有适当的值即可。[...]


2
你声明了三个字符串:
  • p2指向的是sample1
  • str[0]指向的是sample1
  • str[1]指向的是sample2
由于它们都是“字符串字面量”,所以不能被更改,且以只读方式存储。
编译器可以识别到你实际上只有两个唯一的字符串,因此只存储这两个字符串(这取决于实现)。

0x4005f8具体是什么?

在内存中,你可能会找到类似于以下内容:
0x0000004005f8  's'
0x0000004005f9  'a'
0x0000004005fa  'm'
0x0000004005fb  'p'
0x0000004005fc  'l'
0x0000004005fd  'e'
0x0000004005fe  '1'
0x0000004005ff  '\0'
0x000000400600  's'
0x000000400601  'a'
0x000000400602  'm'
0x000000400603  'p'
0x000000400604  'l'
0x000000400605  'e'
0x000000400606  '2'
0x000000400607  '\0'
...
0x7fffffffdb20  0xf8
0x7fffffffdb21  0x05
0x7fffffffdb22  0x40
0x7fffffffdb23  0x00
0x7fffffffdb24  0x00
0x7fffffffdb25  0x00
0x7fffffffdb26  0x00
0x7fffffffdb27  0x00
...
0x7fffffffdb38  0xf8
0x7fffffffdb39  0x05
0x7fffffffdb3a  0x40
0x7fffffffdb3b  0x00
0x7fffffffdb3c  0x00
0x7fffffffdb3d  0x00
0x7fffffffdb3e  0x00
0x7fffffffdb3f  0x00

也就是说:
  • p2 变量:
    • 位于地址 0x7fffffffdb38
    • 值为 0x4005f8
  • str[0] 变量:
    • 位于地址 0x7fffffffdb20
    • 值为 0x4005f8
  • 内存地址 0x4005f8sample1 字符串的开头,即字符 s
  • 内存地址 0x4005f9sample1 字符串的下一个字符,即字符 a
  • ... 0x4005fam
  • ... 0x4005fbp
  • ... 0x4005fcl
  • ... 0x4005fde
  • ... 0x4005fe1
  • ... 0x4005ff\0 或 "nul",表示字符串结束的标志

当你测试 p2 == str [0] 时,实际上是在测试两个变量存储的值是否相同。这些值是字符串的基本地址。它们持有“相同的”字符串,因此具有相同的值。

完全可以将“相同的”字符串(即相同的文本)存储在两个不同的内存位置中,在这种情况下,此测试将失败。

你实际上是在说这两个字符串是“相同的实例”,它们驻留在内存中的相同位置,因此必须具有相同的内容。

... 那么如何打印它呢?

你可以使用 x/1c 逐个字符打印,或者使用 x/1s 打印以 nul 结尾的字符串(gdb 正确处理 C 字符串)。


main.c

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
        char *p2 = "sample1";
        char *str[2] = { "sample1", "sample2" };

        if (p2 == str[0]) {
                printf("true\n");
        }

        return 0;
}

编译:

gcc main.c -o main -g

运行:

$ gdb ./main
[...]
(gdb) start
Temporary breakpoint 1 at 0x4005a5: file main.c, line 4.
Starting program: /home/attie/stackoverflow/56475101/main

Temporary breakpoint 1, main (argc=1, argv=0x7fffffffe418) at main.c:4
4       int main(int argc, char *argv[]) {
(gdb) list
1       #include <stdio.h>
2       #include <stdlib.h>
3
4       int main(int argc, char *argv[]) {
5               char *p2 = "sample1";
6               char *str[2] = { "sample1", "sample2" };
7
8               if (p2 == str[0]) {
9                       printf("true\n");
10              }
(gdb) b 8
Breakpoint 2 at 0x4005cc: file main.c, line 8.
(gdb) c
Continuing.

Breakpoint 2, main (argc=1, argv=0x7fffffffe418) at main.c:8
8               if (p2 == str[0]) {
(gdb) print p2
$1 = 0x400684 "sample1"
(gdb) print str[0]
$2 = 0x400684 "sample1"
(gdb) print str[1]
$3 = 0x40068c "sample2"

从地址0x400684打印三个"字符串":

最初的回答:

(gdb) x/3s 0x400684
0x400684:       "sample1"
0x40068c:       "sample2"
0x400694:       "true"

从地址0x400684打印16个字符:

最初的回答:

(gdb) x/16c 0x400684
0x400684:       115 's' 97 'a'  109 'm' 112 'p' 108 'l' 101 'e' 49 '1'  0 '\000'
0x40068c:       115 's' 97 'a'  109 'm' 112 'p' 108 'l' 101 'e' 50 '2'  0 '\000'

打印存储在p2str[0]str[1]中的地址:

最初的回答:

(gdb) x/1a &p2
0x7fffffffe308: 0x400684
(gdb) x/1a &str[0]
0x7fffffffe310: 0x400684
(gdb) x/1a &str[1]
0x7fffffffe318: 0x40068c

2
C语言允许程序中的静态字符串是唯一的。这意味着编译器可以决定是否优化分配(一次而不是两次)静态字符串“sample1”。您将p初始化为指向存储区域,并且str [0]也是指向同一静态字符串的指针。因此,它们是否相等取决于实现,检查相等性的结果未定义。引用来自6.4.5p6,字符串文字:“未指定这些数组是否不同,只要它们的元素具有适当的值即可。”

1
其他问题已经涵盖了为什么你的字符串相等(在“==”运算符意义上),这里我想直接回答你的问题。 0x4005f8 是存储字符串常量的地址。你可以使用 printf 类型转换 "%p" 打印它,该转换需要一个 void* 参数,你的完整语句应该是:
printf("p2 = %p\n", (void*)p2);
printf("str[0] = %p\n", (void*)str[0]);

将指针转换为void*在GCC中并非必需,但是您可以包含它们以消除警告。然而,在将指针作为可变参数列表的一部分传递时,编译器不会像对于使用原型化的void *参数的函数那样隐式地将其转换为void *,因此这些转换是必需的。

1
你最后一句话破坏了这个答案。 - Bathsheba
@Bathsheba 在这种情况下,char *void * 具有相同的对齐要求,因此并没有太大的问题。 - Sourav Ghosh
在通过变量参数列表传递指针时,实际参数类型与被调用方期望的类型具有相同的对齐和大小是不够的;它们必须是“兼容类型”,这意味着在这种情况下,“剥离顶层cv限定符后相同的类型”。请参见N1570 7.16.1.1p2。该段落末尾有一个特殊情况,允许将char *传递给期望void *va_arg用户,这可能适用于printf,但没有任何东西表明printf使用va_arg - zwol

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接