我来自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
,然后打印出var
,现在神奇地包含buf.release
的值。
为什么我不必为char *var
分配内存?它如何保持我需要的字符串?现在的生活是什么?
答案 0 :(得分:2)
我来自Java的长期经验[...]。我迷惑的第一行是
char *var
,它是一个指向......呃......无处可指的指针?它指向哪里?
没有初始化程序(例如var
)的自动变量的值在为其分配值之前是不确定的。在此之前,您不知道var
的值在何处,并且如果您的程序执行任何依赖于该值的操作,则您的程序会显示未定义的行为。这与Java没那么不同。考虑:
class MyClass {
public void example(void) {
char[] var;
// Line 1
// ...
}
}
{1}在第1行引用的数组是什么? Java没有答案 - 它甚至不允许你提出这个问题。如果在初始化之前尝试编写依赖于var
的值的代码,则编译器会拒绝它。 C的保护性稍差,但在这方面两种语言非常相似。
接下来令人困惑的是函数参数char ** ver中的双指针。它创建一个指向......什么都没有的指针?!
它不会创建任何东西。它指定参数的类型(因此,在调用函数的任何地方,相应参数的预期类型)是指向指针的指针。这与Java没有太大的不同。鉴于......
var
...方法参数 public void example2(Object[] objects) {
// ...
}
不会创建任何内容;它只是指作为参数传递的任何值。一旦你掌握了指针值的事实,就像objects
和int
一样,C版本并不是特别奇怪。它们可以作为参数传递并存储在内存中。
此外,当指针存储在内存中时 - 可能是与变量或数组元素相关联的内存 - 该内存具有一个地址,并且该地址本身可以表示为另一个(不同的)指针值。在您的情况下,该指针的类型是double
,您可以用英语表示为&#34;指向char&#34;的指针。
然后,
char **
以某种方式神奇地复制strcpy(*ver, buf.release)
[...] 进入空指针* var somehow
这里有两件事,但没有魔力。
Thing 1 :与函数参数buf.release
对应的参数为ver
- 变量&var
的地址。此指针的类型与声明的var
类型匹配。从ver
开始,将引用运算符(ver == &var
)应用于*
会产生一个表达式,该表达式指定ver
所执行的同一对象。因此,写入var
会修改调用者中*ver
的值。
在Java中没有直接模拟,但请考虑以下代码:
var
方法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
按值传递给它。 Java和C都有严格的值传递语义,但您可以通过值传递引用(Java)或指针(C),以便以调用者的方式访问和修改引用/指向的事物以后可以观察。
Thing 2 :代码错误。如您所见,List
未在调用者中初始化。将指针传递给函数的通常原因是使函数能够通过指针间接地为其赋值。但是这个功能并没有这样做。相反,它会尝试将数据复制到指向的空间,但会使用var
仍然包含的不确定值,从而产生未定义的行为。
UB 可以显示为或多或少的任何东西,包括程序员的意图,因此代码似乎可行。然而,任何展示UB的计划都是错误的。您不能依赖它来处理任何给定的调用,即使它似乎在程序的同一运行中的前一次调用中表现得如此。程序员的明显意图可以更好地实现:
var
请注意这里的区别:分配到int getKernelVersion(char **ver) {
struct utsname buf;
int errno;
errno = uname(&buf);
*ver = strdup(buf.release);
return errno;
}
,它还会在调用者中设置*ver
的值。
为什么我没有为char * var?
分配内存
很好的问题。实际上,在程序的原始版本中, 需要为var
分配内存。该计划展示了UB,因为你没有这样做。
它如何保存我需要的字符串?
*var
调用显示未定义的行为。调用未定义时strcpy()
是否包含您需要的字符串。如果它似乎这样做,那么你很幸运。或许你可以说你 un 幸运:一个好的分段错误会让你觉得这个程序有问题。
现在的生活是什么?
生活就像一盒巧克力:你永远不会知道你会得到哪一个。实际上有点和UB一样。
答案 1 :(得分:1)
我困惑的第一行是char * var,它是一个指向......的指针......呃......无处可去吗?它指向哪里?
未初始化。并非它指向无处,它实际上并不指向任何地方。
然而,它的(未存在的)值在函数中接下来使用,这是一个错误。
接下来令人困惑的是函数参数char ** ver中的双指针。它创建一个指向......什么都没有的指针?!
不,它是指向指针(指向char)的指针。在你的情况下,它被绑定(因为它是一个函数参数)指向非常var
;因为它不是一个指向常量的指针,所以可以改变原始值(在你的情况下,var
,一个指向char的指针),例如(指向另一个char)。这是最老的C语言之一,具有指向期望保存附加函数返回值的对象的函数参数。
为什么我没有为char * var分配内存?它如何保存我需要的字符串?
是吗?你真的测试了代码吗?
答案 2 :(得分:0)
我猜你找到了
char *getKernelVersion();
更直观。该方法的问题在于您只能通过返回NULL
来发出错误信号。如果您想要返回有关错误的更多具体信息,该怎么办?
int getKernelVersion(char **ver);
另一方面,获取指向指针的指针(即通过引用传递的指针)并将其设置为所讨论的字符串。返回类型留作用作错误代码。
uname(2)
系统调用的工作方式相同。它返回一个错误代码并接收它需要通过引用填充的struct
。
尽管你注意到strcpy
的使用是错误的。它将字节数复制到未指定的位置,从而导致未定义的行为。相反,它应该说*ver = strdup(buf.release);
答案 3 :(得分:0)
我困惑的第一行是char * var,它是一个指针 指向......呃......无处可去?它指向哪里?
现在指针var
没有指向某个地方
接下来令人困惑的是函数中的双指针 参数char ** ver。它创建一个指向......的指针 什么?!
双指针**
指向上面的空指针,即var
。换句话说,**
是var
的堆栈地址。
以某种方式进入空指针* var,然后我打印出var哪个 现在神奇地包含了buf.release的值。
如果您真的检查程序是否正常工作,看似可能是strcpy()
内部检查并将内存分配给var
null
,但我不完全确定。
为什么我没有为char * var分配内存?怎么做的 拿着我需要的绳子?现在的生活是什么?
因此,如果strcpy()
将内存分配给var
,那么毫无疑问。