我来自于Java的长期经验,所以这对我来说是毫无意义的。我有这个:
#include <stdio.h>
#include <string.h>
#include <sys/utsname.h>
#include "uname.h"
int main(void) {
char *var;
getKernelVersion(&var);
printf("%s\n", var);
return 0;
}
int getKernelVersion(char **ver) {
struct utsname buf;
int errno;
errno = uname(&buf);
strcpy(*ver, buf.release);
return errno;
}我困惑的第一行是char *var,它是指向.嗯..。哪里都没有?它指向哪里?
下一件令人困惑的事情是函数参数char **ver中的双指针。它创建了一个指向.什么都没有?!
strcpy(*ver, buf.release)以某种方式神奇地复制了buf.release,就是
struct utsname {
char sysname[]; /* Operating system name (e.g., "Linux") */
char nodename[]; /* Name within "some implementation-defined
network" */
char release[]; /* Operating system release (e.g., "2.6.28") */
char version[]; /* Operating system version */
char machine[]; /* Hardware identifier */
#ifdef _GNU_SOURCE
char domainname[]; /* NIS or YP domain name */
#endif
};以某种方式进入空指针*var,然后打印出现在神奇地包含buf.release值的var。
为什么我不必为char *var分配内存呢?它怎么能抓住我需要的绳子?现在的生活是什么?
发布于 2016-06-26 19:38:30
我来自爪哇的长期经验.我困惑的第一行是
char *var,它是指向.嗯..。哪里都没有?它指向哪里?
没有初始化器(如var)的自动变量的值是不确定的,直到将值赋值给它。在此之前,您不知道var的值指向何处,如果程序执行任何依赖于该值的操作,则会显示出未定义的行为。这与Java并没有太大的不同。考虑:
class MyClass {
public void example(void) {
char[] var;
// Line 1
// ...
}
}var在第1行引用哪个数组?Java没有答案--它甚至不允许你问这个问题。如果您试图在初始化var之前编写依赖于它的值的代码,则编译器将拒绝它。C语言保护程度较低,但这两种语言在这方面非常相似。
下一件令人困惑的事情是函数参数char *ver中的双指针。它创建了一个指向.什么都没有?!
它没有创造任何东西。它指定参数的类型(因此,在调用函数时对应的参数的预期类型)是指向指针的指针。这与Java也没有太大的不同。给予..。
public void example2(Object[] objects) {
// ...
}..。方法参数objects没有创建任何内容;它只是引用作为参数传递的任何值。当您了解指针是值这一事实时,C版本并不特别奇怪,就像int和double的值一样。它们可以作为参数传递并存储在内存中。
此外,当指针存储在内存中时--也许是与变量或数组元素相关的内存--该内存有一个地址,并且该地址本身可以表示为另一个(不同的)指针值。在您的例子中,该指针的类型是char **,您可以用英语将其表示为“指针到字符的指针”。
strcpy(*ver, buf.release)神奇地复制了buf.release..。到空指针*var中
这里有两件事,但没有魔法。
Thing 1:函数参数ver对应的参数是&var --变量var的地址。此指针的类型与声明的ver类型匹配。自ver == &var以来,将取消引用操作符(*)应用于ver将产生一个表达式,该表达式指定与var相同的对象。因此,向*ver写入将修改调用方中var的值。
这在Java中没有直接的模拟,但请考虑以下代码:
public void example3a(List<Integer> list) {
list.append(42);
}
public void example3b(void) {
List<Integer> myList = new ArrayList<>();
example3a(myList);
assert(myList.contains(42));
}方法example3a()能够修改传递给它的List的内容。或者实际上,它的引用按值传递给它的List。Java和C都有严格的逐值语义,但您可以通过值传递引用(Java)或指针(C),以便以调用者稍后可以观察到的方式访问和修改引用/指向的对象。
Thing 2:代码错误。正如您所观察到的,var不是在调用方中初始化的。将指针传递给函数的通常原因是使函数能够通过指针间接地为其赋值。但是这个函数并不能做到这一点。相反,它尝试将数据复制到指向空间,但使用var仍然包含的不确定值,从而产生未定义的行为。
UB可以或多或少地表现为任何东西,包括程序员的意图,因此代码可能看起来是有效的。尽管如此,这是错误的,因为任何程序显示UB。您不能依赖它在任何给定的调用上工作,即使它在同一程序运行中的前一次调用中的行为似乎与预期的一样。程序员的明显意图会更好地实现如下:
int getKernelVersion(char **ver) {
struct utsname buf;
int errno;
errno = uname(&buf);
*ver = strdup(buf.release);
return errno;
}注意这里的区别:分配给*ver,它也设置调用方中的var值。
为什么我不必为char *var分配内存?
问得好。实际上,在程序的原始版本中,您确实需要为*var分配内存。程序显示UB,因为你没有做到这一点。
它怎么能抓住我需要的绳子?
strcpy()调用显示未定义的行为。在调用未定义之后,*var是否持有所需的字符串。如果看起来是这样的话,那你就走运了。或者你可以说你得到了_un_lucky:一个很好的分割错误会告诉你,程序有问题。
现在的生活是什么?
生活就像一盒巧克力:你永远不知道你会买哪一盒巧克力。实际上和UB差不多。
发布于 2016-06-26 18:59:57
我困惑的第一行是char *var,它是指向.嗯..。哪里都没有?它指向哪里?
它未初始化。并不是说它没有指向任何地方,而是实际上它没有指向任何地方。
然而,它(不存在的)值将在函数中使用,这是一个错误。
下一件令人困惑的事情是函数参数char *ver中的双指针。它创建了一个指向.什么都没有?!
不,它是指向指针(指向字符)的指针。在您的例子中,它被绑定(因为它是一个函数参数)来指向那个非常var;因为它不是指向常量的指针,所以指向的原始值(在您的例子中是指向一个字符的指针)可以被更改,例如(指向另一个字符)。这是最古老的C成语之一,它具有函数参数来指向期望保存额外函数返回值的对象。
为什么我不必为char *var分配内存?它怎么能抓住我需要的绳子?
是吗?你真的测试过代码吗?
发布于 2016-06-26 18:48:02
我想你会发现
char *getKernelVersion();更直观。这种方法的问题是,您只能通过返回NULL来发出错误信号。如果您想返回有关错误的更多特定信息,怎么办?
int getKernelVersion(char **ver);另一方面,获取指向指针(即通过引用传递的指针)的指针,并将其设置为所讨论的字符串。返回类型留作错误代码使用。
uname(2)系统调用的工作方式是相同的。它返回一个错误代码并接收它需要通过引用填充的struct。
正如您注意到的那样,使用strcpy是错误的。它将字节复制到未指定的位置,从而导致未定义的行为。相反,它应该说*ver = strdup(buf.release);
https://stackoverflow.com/questions/38041870
复制相似问题