C中的数组和指针,两个问题

Arrays and pointers in C, two questions

此程序适用于 C:

#include <stdio.h>


int main(void) {
    char a[10] = "Hello";
    char *b = a;
    
    printf("%s",b);
}

有两点我希望有所不同。一种是我们在main的第二行写:“char *b = &a”,那么程序是这样的:

#include <stdio.h>


int main(void) {
    char a[10] = "Hello";
    char *b = &a;
    
    printf("%s",b);
}

但这不起作用。这是为什么?这不是用地址初始化指针的正确方法吗?

我遇到的第二个问题是在最后一行我们应该有:printf("%s",*b) 所以程序是这样的:

#include <stdio.h>


int main(void) {
    char a[10] = "Hello";
    char *b = a;
    
    printf("%s",*b);
}

但这给出了分段错误。为什么这不起作用?我们不是应该在指针前面写“*”来获取它的值吗?

C有一个特殊的规则,当你写

char *b = a;

你得到的效果和你写的一样

char *b = &a[0];

也就是说,您会自动获得指向数组第一个元素的指针。每当您尝试获取数组的“值”时,都会发生这种情况。

Aren't we supposed to write "*" in front of a pointer to get its value?

是的,如果您想获得 b 指向的单个字符,则需要 *。此代码

printf("first char: %c\n", *b);

将打印字符串的第一个字符。但是当你写

printf("whole string: %s\n", b);

你得到了整个字符串。 %s 打印多个字符,它需要一个指针。在 printf 内部,当您使用 %s 时,它会循环并打印字符串中的所有字符。

扩展史蒂夫的回答(这是正确的答案)...

这是他说的特殊规则:

6.3.2.1 Lvalues, arrays, and function designators
...
3 Except when it is the operand of the sizeof operator, the _Alignof operator, or the unary & operator, or is a string literal used to initialize an array, an expression that has type ‘‘array of type’’ is converted to an expression with type ‘‘pointer to type’’ that points to the initial element of the array object and is not an lvalue. If the array object has register storage class, the behavior is undefined.
C 2011 Prepublication Draft

数组很奇怪,不像其他类型。在 struct 类型等其他聚合类型中,您不会得到这种“衰减到指向第一个元素的指针”的行为。您不能像使用 struct 类型那样使用 = 运算符分配整个数组的内容;例如,你不能做类似

的事情
int a[5] = {1, 2, 3, 4, 5};
int b[5];
...
b = a; // not allowed; that's what "is not an lvalue" means

为什么数组很奇怪?

C 派生自一种名为 B 的早期语言,当您在 B 中声明数组时:

auto arr[5];

编译器留出一个额外的词来指向数组的第一个元素:

     +---+
arr: |   | ----------+
     +---+           |
      ...            |
     +---+           |
     |   | arr[0] <--+
     +---+
     |   | arr[1]
     +---+
     |   | arr[2]
     +---+
     |   | arr[3]
     +---+
     |   | arr[4]
     +---+

数组下标操作 arr[i] 定义为 *(arr + i) - 给定存储在 arr 中的起始地址,从该地址偏移 i 元素并取消引用结果。这也意味着 &arr 会产生与 &arr[0] 不同的值。

在设计 C 时,Ritchie 希望保留 B 的数组下标行为,但他不想为该行为所需的单独指针预留存储空间。因此,他没有存储单独的指针,而是创建了“衰减”规则。当你在 C:

中声明一个数组时
int arr[5];

唯一留出的存储空间是数组元素本身:

     +---+
arr: |   | arr[0]
     +---+ 
     |   | arr[1]
     +---+
     |   | arr[2]
     +---+
     |   | arr[3]
     +---+
     |   | arr[4]
     +---+

下标操作arr[i]仍然定义为*(arr + i),但不是arr中存储一个指针值,而是一个指针值从表达式 arr 计算 。这意味着 &arr&arr[0] 将产生相同的地址值,但表达式的类型将不同(分别为 int (*)[5]int *)。

此规则的一个实际效果是您可以在指针表达式和数组表达式上使用 [] 运算符 - 给定您的代码,您可以编写 b[i] 并且它的行为与 a[i].

另一个实际效果是,当你将数组表达式作为参数传递给函数时,函数实际接收的是指向第一个元素的指针。这就是为什么您经常必须将数组大小作为单独的参数传递的原因,因为指针仅指向指定类型的单个对象;无法从指针值本身知道您是否指向数组的第一个元素,数组中有多少元素等。

数组不携带任何元数据,因此无法在运行时查询数组的大小、类型或任何其他内容。 sizeof 运算符是在编译时计算的,而不是运行时。