【问题标题】:Which one is the correct address of this char array in C?哪个是 C 中这个 char 数组的正确地址?
【发布时间】:2014-07-17 12:25:26
【问题描述】:
#include<stdio.h>
int main()
{
  char *str1 = "computer";

  printf ("%p\n", (void *)   str1);   // i
  printf ("%p\n", (void *)  &str1);   // ii
  printf ("%d\n",           *str1);   // iii
  printf ("%p\n", (void *)  *&str1);  // iv
  printf ("%p\n", (void *)  &*str1);  // v

  return 0;
}

我知道&amp; 是“地址”运算符,* 是“地址中的值”运算符。 在我看来,

i. 这是 char 数组的地址,因为数组的名称代表第一个元素的地址。

ii.我不明白这一点,因为地址的地址似乎令人困惑。

iii.这应该表示地址处的值(即computer),但格式说明符是%p,并且输出中会打印一个随机数。

iv. 这应该是地址地址的值。这再次令人困惑。

我在输出中得到随机值。我知道其中一些可能是由于未定义的行为。但如果有人能澄清这些的含义,我将非常感激。

我也想说

printf ("%p\n",  **str1);
printf ("%p\n",  &&str1);

给出编译失败的错误。

【问题讨论】:

  • 正确答案是i,由stdcall给出
  • @stdcall 谢谢,其他人是什么意思?
  • "address of address" - 没有这样的东西。例如:ii 是错误的。 &amp;str1 产生 指针 str1 的地址。该指针在中保存的地址是char数组的地址,但指针本身有自己的地址。
  • 您需要printf ("%s", str1);。 s 不是 d,因为您正在打印字符串,而不是有符号整数。
  • iii *str1 是 'c' 可能是 99 的 ascii 值

标签: c arrays pointers char


【解决方案1】:

示例代码中唯一的数组是由字符串字面量产生的数组:

"computer"

在大多数情况下,1 C 中的字符串文字指示编译器在内存中的某处生成(或表现得好像它已经生成)一个数组,该数组的内容用字符填充组成字符串文字。不允许在数组元素上写字——就好像数组的类型为 const char [<em>N</em>] 以对应适当的整数常量 N——但数组本身实际上具有类型 char [<em>N</em>]。 (在大多数现代编译器上,如果可能,该数组可能会放置在物理只读存储中,例如 ROM 或文本空间,尽管许多编译器都有可能影响这一点的指令。正如其他人指出的那样,规则有点与 C++ 不同。整数常量 N 由字符串文字中的字符数确定,并添加了终止 '\0' char。)

一般来说,给定一个类型为“数组N of T”的变量v(对于某些类型T),您只需将&amp;v写成得到它的地址。这是一个“指向 T 的数组 N 的指针”类型的值。这也适用于字符串文字:2

char (*p_a)[6] = &"hello";

这里的p_a 是一个指向数组的指针。这和之间的区别:

char *p_c = "world";

p_c 是一个指向单个char 的指针,而不是指向(整个)数组的指针。因此p_c[1] 是该数组中的第二个 char,即'o'。但是p_a[1] 将是第一个数组"hello" 之后的整个6-char-long 数组,如果有这样的数组(没有这样的数组,因此尝试访问p_a[1] 会产生未定义的行为)。

那么,从字面上看,所问问题的答案是“都不是”。这些表达式都没有同时指向整个数组!

因为我们知道"computer" 由 9 个chars 组成——八个可见的chars 和一个终止'\0'——我们可以获取存储在str1 中的值,它是一个指向9 个chars 中的第一个,并将其(通过强制转换和/或赋值)转换为正确的类型char (*)[9],以获得“(整个)数组的地址”:

char (*answer_1)[9] = (void *)str1;

或:

((char (*)[9]) str1); /* answer2: produce the value, then ignore it */

例如。

不过,关于 C 中的指针和数组的一点是,指向数组的 第一个元素 的指针,就所有实际目的而言,与指向 的指针“一样好”。 em>整个数组。因此,简单的str1(指向 9-char-long 数组的第一个元素的指针)与指向整个 9-char-long 数组的指针“一样好”。

对于刚接触 C 的人来说,这一切听起来很奇怪。很久以前,我制作了一个图形来显示关键区别;可以找到here。区别的本质是“圆的大小”而不是“地址值”本身。

列表中的第 2 项(或者我应该说“ii”)&amp;str1 使用了这样一个事实,即您有一个实际对象 str1,类型为“指向char”的指针。地址运算符&amp; 为您提供指向该对象的指针:类型为“指向(指向char)的指针”或char ** 的值。值本身可以被认为是指向变量str1 的箭头,而存储在str1 中的值本身可以被认为是指向"computer" 中的c 的箭头。


1这里的主要例外是当字符串字面量用作初始值设定项时。例如:

char ch_array[9] = "computer";

完全填充 9-char-long 数组ch_array,而不必在任何地方创建第二个数组。在这些情况下,您可以控制结果数组的实际const-ness; ch_array 不是 const 并且 是 您可以写入的东西,因此您可以更改此数组中的字母,例如,如果您愿意,可以将其设为“catputer”。

C 标准还允许您将数组中的一个元素设置为“太短”,即无法容纳终止符 '\0'。在这种情况下,终结符根本不存储在任何地方。没有标准的方法可以在不创建这样的对象的情况下执行此操作,尽管有一个提议,在字符串文字中使用最终的 \z 转义以获得相同的结果,但没有成功。

注意字符串连接:

char *x = "com" "put" "er";

不在部件之间插入终止符'\0':在 连接之后添加终止符。而且,由于在此处生成的匿名数组上写入会产生未定义的行为,因此编译器也可以(并且好的编译器可以)share 字符串文字,可能带有“尾部共享”,3 这样:

char *s1 = "hello world", *s2 = "world";

可能只生成一个数组,包含"hello world" 字符串,并使s2 指向该字符串中的w。

2早在 1980 年代,很多编译器都犯了这个错误。公平地说,当时没有 C 标准:最初的 ANSI C 标准于 1989 年 12 月问世,所以直到 1990 年才有任何官方规则可以适用于编译器编写者。

3“尾巴共享”不是一个艺术术语,只是我在这里编造的一个短语,试图描述这个过程。

【讨论】:

  • 您实际上不需要为从字符串文字初始化而设置异常。 as-if 规则意味着您仍然可以说文字存在。
  • @MattMcNabb: 是的,但这也给了我一个地方来了解允许您省略终结符的特殊情况。 :-)
【解决方案2】:

让我们按顺序来:

  1. str 返回指向数组第一个字符的指针
  2. &amp;str 返回一个指向指针的指针(即它指向变量 str 所在的位置)
  3. *str 取消引用指针,并返回指向的值,换句话说,它返回字符串中的第一个字符(所以它根本不是指针)
  4. *&amp;str 返回与 1 相同,因为取消引用和地址操作符相互抵消
  5. &amp;*str同上,address-of和dereference相互抵消

第 4 点和第 5 点的区别在于表达式的求值顺序,但结果是一样的。

在您使用 char 指针的情况下,“正确”的方式是 1。


至于**str 和&amp;&amp;str 的错误,因为*str 导致char,这不是您可以取消引用的类型。带有双与号的那个是因为它是逻辑和运算符&amp;&amp; 应用于一元表达式而不是二元表达式。


一个不相关的点:字符串文字是字符的常量数组,其大小等于字符串中的字符数加上字符串终止符的数量。这意味着拥有一个普通的非常量指针是错误的,声明应该是

const char *str = "computer";

或可能(不知道它是否适用于C)

char const *str = "computer";

【讨论】:

  • 在 C 中(与 C++ 不同),字符串文字的类型是 char *,而不是 const char *,尽管禁止写入它们。这是为了与 K+R C 向后兼容。一些编译器可以选择将类型设置为 const char *(例如 gcc 的 -Wwrite-strings),但是为大量旧代码打开它可能会引发大量后果。
  • @Zack:字符串文字在这两种语言中都没有指针类型。它们具有字符类型数组,在 C++ 中限定为 const,在两者中均具有可共享和逻辑常量。
  • @Deduplicator 这种详细程度只会让 OP 理解水平的人感到困惑。
  • @Zack 模糊数组和指针之间的区别是混淆任何学习 C 的人的最佳方式。作为证据,看看有多少关于 SO 的帖子认为他们理解数组的人被绊倒了将二维数组传递给函数
  • 从技术上讲,&amp;&amp;str 失败并不是因为第一个 &amp; 应用于 &amp;str,而是因为 &amp;&amp; 被标记为单个 &amp;&amp; 运算符,这在此上下文中是不允许的(标准 C 没有一元 &amp;&amp;)。 (GNU C 有一个扩展,其中一元 &amp;&amp; 可用于提取代码空间地址,但这是 gcc 的特殊之处。)
【解决方案3】:

您的所有示例都会导致未定义的行为,因为带有格式说明符 %d 的 printf 需要 signed int,而不是指针。

作为stdcall points out,数据指针的正确格式说明符(尽管仅适用于指向字符的指针和void-指针,其余的必须严格来说必须转换为它们中的任何一个)是%p,它将以实现定义的格式打印指针。

这是char** 类型的右值:&amp;str1 转换为void* 并使用%p 打印
这些是char* 类型的左值:str1 *&amp;str1 用%s 打印字符串,用%p 打印指针值
这是char* 类型的右值:&amp;*str1 同上
这是char 类型的左值:*str1 使用%c 打印

所有char* 都指向同一个对象。

请注意,尽管由于历史原因字符串文字的类型为 char[],但它们是可能共享的常量:尝试更改它们会导致 UB。

【讨论】:

    【解决方案4】:

    printf ("\n%d", *str1); // iii

    不打印随机数,而是第一个字符的ascii值

    *str1 实际上是第一个 char 'c' == 99 的值

    使用 *(str1 + i) 访问 str1 的元素 i

    【讨论】:

      【解决方案5】:
      printf ("%d", str1);   // i
      

      这是你的“字符串”的地址,你的char 数组存储在其中(相当于&amp;str[0])。 str1 的类型显然是 char* 与 %d 说明符不匹配,但大多数实现会将地址输出为整数。

      printf ("\n%d", &str1);   // ii
      

      str1指向str1中第一个char的地址(即&amp;str[0]),所以&amp;str1指向str1:

      &str1 -> str1 -> "computer"
      
      printf ("\n%d", *str1);   // iii
      

      这完全等同于str1[0],即'c'。由于您指定了%d 说明符,因此输出的值是'c' 的ansi 值。

      printf ("\n%d", *&str1);  // iv
      printf ("\n%d", &*str1);  // v
      

      嗯,*&amp;a 和 &amp;*a 在大多数(所有?)情况下都等同于 a,所以在这里你只是得到第一个行为。

      printf ("\n%d", **str1);
      

      str1 是一个指针,因此您可以取消引用它以获得'c',但*str1 是'c',您不能取消引用char。

      printf ("\n%d", &&str1);
      

      &amp;str1是str1的地址,但它不是变量所以它没有任何地址所以&amp;&amp;str1是不可能的(str1的地址的地址不存在)。

      正如其他答案中提到的,输出指针的正确方法是%p。您会在所有情况下获得随机值(通常除了iii),因为str1 并不总是具有相同的地址。

      【讨论】:

        猜你喜欢
        • 2020-09-30
        • 2014-05-21
        • 1970-01-01
        • 2019-10-27
        • 2012-10-16
        • 1970-01-01
        • 2023-03-16
        • 2018-10-08
        • 1970-01-01
        相关资源
        最近更新 更多