指针持有地址以外的东西? [C]

Pointers holding something other than an adress? [C]

我已经在大学学习了几个月的 C,但是我错过了一个关于指针的讲座,所以我试图通过在线学习来弥补它,我以为我明白了 - 但我只是偶然发现对我来说非常恼火。

我知道指针只包含它们指向的地址 - 例如,如果到目前为止我理解正确,我有:

int *pointer;
int number = 30;
pointer = &number;
printf("Number at location: %d", *pointer);

这工作正常,应该如此。我将变量号的地址分配给指针,然后最后通过取消引用指针并从地址中获取实际值来打印它。令我恼火的是字符指针。

我已经阅读了字符串 arrays/pointers,所以我尝试了一些东西,当我注意到一些奇怪的事情(至少对我来说是这样)发生时,还有 int 指针:

char* pointer;
char array[] = "Dingleberry";
pointer = array;
printf("%s\n", pointer);
return 0;

我知道我没有直接分配地址,但如果我没记错的话,对于数组,不需要与指针一起使用 - 无论如何 - 这段代码按预期工作,它打印出来 "Dingleberry" .我现在的问题是……为什么?不取消引用的指针不应该只保存值的地址吗?如果我在这里取消引用,程序会崩溃,但如果我使用 &,它确实会显示地址。

我在编译时没有收到任何警告。另外,如果我使用:

它不应该工作吗?
printf("%c", pointer); 

只收到一封信? (我的意思是,尝试这样做确实会显示警告 - 但我有兴趣变得更好并排除我最可能的愚蠢误解。)

它与指针类型或其存储的内容无关,它是 printf() 函数的 "%s" 说明符,它需要一个指向 c 字符串的指针,即 nul 终止的字节序列。

如果要打印指针地址,使用"%p"说明符

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

如果你想要它指向的对象的地址,在这种情况下数组只是

printf("%p\n", (void *) pointer);

注意:对于通用指针,请使用 void *,因为它无需转换为任何指针类型即可转换。

您在这里缺少 %s 格式说明符的属性。

引用 C11 标准,章节 §7.21.6.1,fprintf()

s              If no l length modifier is present, the argument shall be a pointer to the initial element of an array of character type.280) Characters from the array are written up to (but not including) the terminating null character. [...]

因此,根据定义,%s 需要一个指向 null-terminated 数组的指针,它会打印出数组的内容,直到空终止符。因此,您不需要像 %d 格式说明符那样取消引用指针。

抓紧你的袜子,这会有点颠簸。

首先,C 中的 string 只是一个字符值序列,后跟一个 zero-valued 终止符。这些字符值可以是单字节字符(用 char 类型表示,常见编码为 ASCII 和 EBCDIC)或 multi-byte 字符(每个字符由 序列 表示一个或多个 char 类型值,用于 UTF-8 等编码)。单字节和多字节字符串的终止符是单个 0 值字节。 C 还支持 "wide" 字符类型 wchar_t 用于编码(我认为)UTF-16。

字符串存储charwchar_t的数组。该数组必须足够大以存储字符串中的所有字符 加上 零终止符。因此,字符串 "Hello"6 个字符值的数组 - {'H', 'e', 'l', 'l', 'o', 0}。所有字符串都是 char(或 wchar_t)的数组,但并非所有 char(或 wchar_t)的数组都是字符串 - 零终止符 必须 为代表字符串的数组存在。

字符串 文字 "Hello""Monday""Sun" 存储为 char 的数组,以便它们可见在程序的整个主体上,它们的生命周期从程序启动一直延伸到程序退出。尝试修改字符串文字的内容会调用 未定义的行为;您的代码可能会出现段错误,或者它可能完全按照您的意图行事,或者它可能会做其他事情并使您的系统处于不良状态。大多数常见平台将字符串文字存储在 read-only 内存段中,因此尝试更新它们会导致段错误。

当你像这样声明一个指针时

char *foo = "Hello";

all foo包含的是字符串第一个字符的地址。当您使用 %s 转换说明符将此指针传递给 printf 时,printf 将从该地址开始,然后 "walk" 沿着字符串向下打印每个字符,直到它看到 0 终止符.大多数处理字符串的库函数都以相同的方式工作;他们获取字符串第一个元素的地址,然后 "walk" 向下直到他们看到终止符。

您还可以声明一个 char 的数组,并像这样向其中存储一个字符串:

char foo[] = "Hello";

这一次,foo 是一个包含字符串 "Hello"char 的 6 元素数组。与字符串文字 "Hello" 不同,您可以根据自己的喜好修改 foo 数组的内容(尽管您只能向其中存储 5 个或更少字符的字符串 - 数组不会自动随着您添加或删除数据而增长或缩小)。

请注意,= 运算符仅在 初始化 声明中的数组时有效;在声明之外,您不能使用 = 运算符将一个数组的内容复制到另一个数组。例如

char foo[10];
...
foo = "Hello"; // bzzzt - no good

不会工作。大多数情况下,数组类型的表达式(如字符串字面量"Hello")被隐式转换("decay")为指针类型,表达式的值将是数组首元素的地址大批。所以在行

foo = "Hello";

您正在尝试将字符串文字 "Hello" 地址 分配给 数组 foo ,这将导致编译器出错。相反,您必须使用 strcpystrcatsprintf 等库函数来写入或更新存储字符串的数组。

然而,

char *foo;
...
foo = "Hello";

工作正常,因为在这种情况下 foo 只是指向 char 的指针,而不是 char 的数组。

Also, shouldn't it work if I were to use:

printf("%c", pointer); 

to only get one letter?

不,打印一个字母是行不通的,因为 default argument promotion. 好吧,它 可能 打印一个字母,但可能不是第一个字母string 您的指针可能指向。

简而言之:对于可变参数函数 - 一个采用可变数量参数的 C 函数,例如 printf() - 每个参数 提升 为固定大小.所以被调用的函数无法直接告诉每个参数到底是什么。这就是为什么 printf() 在格式字符串中有格式说明符——它们告诉被调用函数参数实际上是什么。这也是为什么对参数使用不正确的格式说明符被视为未定义行为的原因 - 如果您使用 %s 格式说明符告诉被调用函数 int 是指向字符串的指针,该函数将取消引用int 的提升值并尝试将它指向的内存视为字符串,如果它甚至是内存,它可能不是。

因此,即使结果是未定义的行为,也可能会打印出 char 的值,很可能是指针本身包含的最低位字节。这甚至可能是匹配字符串中第一个字符的字母。