打印一个字符指针...会发生什么?

5

我是C语言的新手,有一个关于字符指针和它会打印什么的问题。看一下:

int main()
{
char *p1="ABCD";
p1="EFG";
printf ("%s",p1);
return 0;
}

它将打印出EFG。

现在:

int main()
{
char *p1="ABCD";
//p1="EFG";
printf ("%s",p1);
return 0;
}

同时它会给你ABCD

我不明白的是*p1到底是什么?
它是一个存储char值的地址还是一个数字?
它是一个char吗?
现在*p1里面有什么?为什么它是const


2
p1 是指向字符串中第一个字符的指针。就这样。 - Oliver Charlesworth
即使未定义,它也是char和const类型,给定字符串的第一个字符。 - Jason Hu
深入解释@OliverCharlesworth所说的:当C语言出现时,学习它的人通常已经熟悉汇编语言,他们直觉地理解指针只是一个包含某个地址的变量。在字处理机上,char*可以是一个单词和字符偏移量的地址。但是随着语言的发展,指针的概念也得到了演变。在C++中,指针已经被推广为_迭代器_,它们本身可以是复杂对象。从底层考虑指针实际上是什么并不是很有用。指针指向某个东西。无论如何,这都不重要。 - ganbustein
1
那不太酷。在 C 中生存意味着与指针共存。了解指针的确切含义非常重要,而且不能有任何偏见。在 C 中没有黑盒可用。 - Jason Hu
8个回答

3

来自C标准:

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf


(注:本文为格式化的文本,无需翻译)
EXAMPLE 8 The declaration

  char s[] = "abc", t[3] = "abc";
  defines ‘‘plain’’ char array objects s and t whose elements are initialized with character string literals.

This declaration is identical to

  char s[] = { 'a', 'b', 'c', '\0' },
  t[] = { 'a', 'b', 'c' };

The contents of the arrays are modifiable. On the other hand, the declaration

  char *p = "abc";

defines p with type ‘‘pointer to char’’ and initializes it to point to an object
with type ‘‘array of char’’ with length 4 whose elements are initialized with a
character string literal. If an attempt is made to use p to modify the contents
of the array, the behavior is undefined.

换句话说:
1)char *p1;声明了一个指针变量(p1)。指针p1可以更改。
2)char *p1 = "ABCD";声明了p1(与“1”相同),并初始化它所指向的数据(将其初始化为只读的5个元素字符数组“ABCD \ 0”)。
因此,您可以更改地址“p1”所指向的位置(可以将其指向“EFG”,分配给NULL等等)。但是您不能安全地更改数据本身。如果尝试覆盖“ABCD”,可能会出现“访问冲突”的错误。确切的行为是“未定义的”-任何事情都可能发生。
3)char p2 [] =“ ABCD”;声明了一个数组变量(p2)。它分配了5个字符,并将数据初始化为“ABCD \ 0”。
该数组是可读写的(可以更改数组中的数据),但是变量“p2”无法更改(因为它不是指针,您无法将p2更改为指向其他地址)。
4)那么char *指针和char array []之间的“区别”是什么?这里有一个很好的讨论: http://c-faq.com/aryptr/aryptrequiv.html

N1124是C99标准的草案。N1256是更为现行的C99草案,它包括了所有三个技术勘误。N1570是2011年ISO C标准的最新公开草案。 - Keith Thompson
我引用的文本在两个版本中完全相同:我只是碰巧先找到了草案的链接。我更新了对N1256的引用。所有相关的C/ISO文档列表在这里:http://www.open-std.org/jtc1/sc22/wg14/www/projects#9899。坦白说,我建议你只读[K&R](http://www.amazon.com/C-Programming-Language-2nd/dp/0131103628)。 - paulsm4
K&R 对于某些目的来说非常优秀,但是 (a) 它并不是语言的严格定义(大部分内容更像是教程),而且 (b) 第二版 "K&R2" 是针对 1990 年版本的语言。自那以后已经有两个更新的标准,分别是 1999 年和 2011 年。但是,是的,K&R 几乎肯定比 ISO 标准更适合 学习 这种语言。 - Keith Thompson

2
int main()
{
    char *p1="ABCD";
    p1="EFG";
    printf ("%s",p1);
    return 0;
}

这是一段缺少必要的 #include <stdio.> 的代码。没有它,你的编译器可能会让你调用 printf,但不能保证能正常工作。还有一个小问题:int main() 应该改为 int main(void)
实际上,这个程序中有很多细节,有些还比较微妙。
char *p1 = "ABCD";

""ABCD"是一个字符串字面值。它指定了一个类型为char[5]的匿名数组对象(4个字符加上一个标记字符串结尾的'\0'空字符)。该对象具有静态存储期,这意味着它在程序的整个执行期间都存在(与局部变量不同,例如当它超出范围时就不存在了(这是一种轻微的过度简化))。

在大多数情况下,数组表达式会被隐式转换为指向其第一个元素的指针。因此,p1被初始化为指向那个匿名数组对象的第一个字符'A'

p1应该被定义为const char *p1 = ...。实际上并不是必需的,但它将帮助编译器捕捉到任何试图修改数组对象的尝试。(该对象不是const,但修改它会产生未定义的行为。)

"
p1 = "EFG";

现在存储在中的值被覆盖并替换为一个新的指针值,这次指向字符串字面量"EFG"'E'。 (所以初始化它没有意义,但没关系。)
printf("%s", p1);

在这里,我们将指针值p1的值传递给printf函数。
此时,*p1的值为'E',类型为char。但是,printf能够打印整个字符串"EFG"。它使用指针算术来实现。在printf的实现中,有一个循环,它获取传入的指针值,解引用它以获取要打印的字符值,然后增加指针,使其指向字符串的下一个字符('F')。该循环继续进行,直到达到标记字符串结尾的'\0'空字符;该空字符不会被打印出来。
由于在C语言中,数组可以说是“二等公民”,大多数对数组对象的操作都是使用指向数组元素的指针来遍历数组。(你也可以使用索引符号arr[i],它本质上做的是相同的事情。)printf不能直接访问数组对象本身;它依赖于指针(指向其第一个元素)来确定数组在内存中的位置,并依赖于'\0'终止符来确定字符串的结尾。
第二个程序与第一个程序相同,只是p1没有被重新赋值,因此打印出了字符串"ABCD"
在C语言中,数组和指针之间的关系可能会令人困惑。comp.lang.c FAQ的第6节非常好地解释了这一点。

2
我不明白的是*p1到底是什么?它是一个包含char值的地址还是一个char本身?如果您在声明中提到*p1,那么...
char *p1 = "ABCD";    

那么*表示p1是指向char(实际上在这种情况下是const char)的指针。 p1只是指向字符串字面量ABCD的第一个字符。

如果您问的是*p1,就像

printf("%c", *p1); 

那么*在这里是一个间接操作符,*p1表示指针p1所指向的字符值为A*p1等同于p1[0]

 p1 = "DEF";  

p1 指向字符串字面量 DEF 的第一个字符。

为什么要使用 const

字符串字面量存储在内存的只读区域。

char *p1 = "ABCD";  

等同于

char const *p1 = "ABCD";

这意味着您无法修改字符串常量。
*p1 = 'a'; // WRONG. Invokes undefined behavior.  

1

p1 是一个 char 指针,它保存了使用 = 运算符 分配 给它的字符串的 基地址起始地址第一个元素的地址

在您的第一段代码中,您将 "ABCD" 的基地址分配给了它,稍后又将 "EFG" 的基地址赋值给了它(覆盖了前一个值),最后将其打印出来。因此,打印的是最新的值 ["EFG"]。

在第二个案例中,您将 "ABCD" 的基地址赋给了它并将其打印出来。因此,它打印出了 ABCD。 值得一提的是,printf() 中的 %s 格式说明符期望一个以空字符结尾的字符串的起始 地址,这里由 p1 指向。

根据const部分,指向p1stringvalue在这种情况下是常量,因为字符串字面值通常存储在只读内存位置。这意味着不允许更改值,如p[1] = 's'。然而,指针p1不是const,因此可以正确地重新分配新值p1="EFG";

它如何回答这个问题? - Manjunath N
谢谢。为什么在我写 printf ("%s",*p1); 时堆栈会溢出呢?难道它不应该只打印“A”吗? - Behrus58
@Behrus58 注意到 null-terminated 这个术语的提及。这是字符串结尾的标志,也就是说,在此之前 printf() 将会打印。 - Sourav Ghosh
@Behrus58,我希望你指的是情况1。如果是这样的话,那么是正确的,因为“ABCD”的基地址现在被“EFG”的基地址覆盖了[通过p1="EFG";],第一个字符确实是E - Sourav Ghosh
@Sourav Ghosh 所以 *p1 总是它地址的 [0] 块,只要你可以改变地址,你就可以改变 *p1[0] ... 我是对的吗? - Behrus58
显示剩余9条评论

1

p1 是一个字符指针,它保存编译器将 "ABCD" 存储在内存中的地址。该地址是数组中第一个字符的地址。

当你有 char *p1 = "ABCD"; 时,你只是将 p1 中保存的地址赋值给它。 当你重新赋值 p1 = "EFG"; 时,你正在重新分配指针以保存一个新地址(即 "EFG" 的地址)

printf ("%s",p1); 打印了 p1 指向的位置的内存内容。


1

Initially p1 is declared as a char pointer and points to "ABCD" (base address of "ABCD").

Later the pointer points to "EFG" (contains the base address of "EFG")

+----+      +---+
|ABCD|      |EFG|
+----+      +---+
 1000        2000

char *p1="ABCD";
         +----+
before   |1000|
         +----+
          p1
p1="EFG";
After    +----+
         |2000|
         +----+
          p1


难道不是反过来的吗? :D ?! - Behrus58

0
我不太明白的是 *p1 到底是什么!它是一个包含 char 值的地址还是一个数字?它是一个 char 吗?*p1 目前是什么?

*p1 的类型是 char,其值为字符'A'

为什么它是 const?

"ABCD" 这样的字符串文字被放置在只读存储器中。通过 p 修改它们会导致未定义行为。这就是为什么 *p1 是只读的,即它是一个 const 的原因。


0
我不明白的是*p1到底是什么!它是一个包含char值的地址还是一个char?现在*p1里面有什么?
在你的第一个程序中,*p1的值是p1[0],即值为'E'char;在你的第二个程序中,它的值是'A'
为什么它是const?
在C中它不是const(在C++中是),但是字符串字面量被指定为在C中不可修改,这意味着尝试修改*p1会导致未定义的行为。

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