C 语言是没有传址的, 对吗?

2015-03-15 13:25:01 +08:00
 herozem

最近编写C程序发现一个问题, 传统教材里说的传址, 实际上并没有这回事, 是吗? 比如说

void print(int sum);
调用函数的时候函数内的sum是实参的一份复制(int sum = 实参), 但实际上, 即便是传递指针:
void print(int *sum);
也只是把指针复制了一份, 因此可以改变指针所指向的内容, 但是并不能改变作为实参的指针本身的内容, 是吗?例如:
#include <stdio.h>
#include <stdlib.h>

typedef struct Node {
  int num;
  struct Node *next;
} Node;

void init(Node *p)
{
  p = malloc(sizeof(Node)); /* Header */
  p->num = 1;
  p->next = NULL;
}

int main(void)
{
  Node *p;
  init(p);
  printf("%d\n", p->num);

  return 0;
}

这一段程序如果编译运行的话, 会core dumped, 就是因为在main函数执行完init(p)的时候init函数内的p是实参p的一份拷贝, 所以改变的实际上是拷贝版本, 最终导致printf的时候访问到了非法地址而造成的。这是我的猜想, 是这样子的吗?

3454 次点击
所在节点    C
33 条回复
zhicheng
2015-03-15 17:16:16 +08:00
指针是一个“类型”,它的值是它指向的内存地址。你的代码之所以是错的,是因为你改变的是指针的值(地址的值),而不是指针(地址)指向的内存。如果你实在理解不了,就定义一个 ptr_t (typedef void * ptr_t)。遇到指针的参数用这个。
herozem
2015-03-15 19:07:10 +08:00
@Reficul 经常乱+1

@exiahan 是, 指来指去有点晕
herozem
2015-03-15 19:11:47 +08:00
但是现在有这么一个新的问题:

```c
void printlist(listnode *list)
{
listnode *item = list;

while (item->next != NULL) {
printf("num = %d =>", item->num);
item = item->next;
}
}
```

传入的list是这样子的:

-------------- ----------------
(list)--->| 1 | ----|------>| 100 | -----|------> NULL
--------------- ----------------

运行printlist也会coredump, 这里有什么不妥吗?
xieyudi1990
2015-03-15 19:32:39 +08:00
@herozem 你没有检查item. 遍历到最后一个node之后, item可能无效. 然后cpu用这个可能无效的地址加上结构体的偏移产生一个无效的有效地址去访存, 然后mmu就抱怨了, 指令在完成时产生异常.

跟着执行流想想就知道问题在哪, 看不出来大不了调汇编.
MntCw
2015-03-15 21:09:10 +08:00
你传的是一个地址的值,但是你实际要传的是指向这地址的指针。
choury
2015-03-15 21:59:48 +08:00
@herozem 请提高自己的debug能力
gerorim
2015-03-15 23:30:45 +08:00
geeti
2015-03-16 05:47:10 +08:00
需要**p
毕业时候看了Google推荐的面试教材,interview exposed,才考虑到这个问题。
blackworld
2015-03-16 23:32:46 +08:00
@geeti 为什么需要**P?
typedef struct Node {
int num;
struct Node *next;
} Node;
这个里面的 “struct Node *next”
这句话怎么理解比较好?
blackworld
2015-03-16 23:34:33 +08:00
@noark9
为什么需要**P?
typedef struct Node {
int num;
struct Node *next;
} Node;
这个里面的 “struct Node *next”
这句话怎么理解比较好?
Reficul
2015-03-17 00:51:30 +08:00
@blackworld
把一个指针传给函数里面之后,其实是把这个指针指向的地址赋值给了函数里的另一个变量。不管你怎么修改他,原本函数外的那个指针的值是不会变得,所以还是指着原来的地方。你要修改指针指向的目标,那你得传入函数的是一个指向该指针的指针,传入的是需要修改的指针的地址。
其实和函数传入a这个变量,不能修改他一样,要修改他需要传入a的地址。

那个typedef struct Node {} Node;是定义一个叫Node的结构体,然后再typedef成一个叫Node的类型。两个Node不是一个概念。在结构体里面那一行就是定义一个Node类型的结构体指针。不能直接Node *next;貌似是因为在这里面的时候,typedef还没被执行,还不是一个类型,要用他只能叫他全名-结构体Node。

说的比较乱,很多自己的感受,不一定正确。
geeti
2015-03-17 02:58:48 +08:00
@blackworld 说啥也没用,自己写个最简单的code,然后根据地址一步一步跳着看就明白了
noark9
2015-09-15 22:55:33 +08:00
@blackworld
为什么是 **p ,因为你需要修改的是 *p 所以要取 *p 的地址的值给函数去修改
比如,你要修改一个 int 的时候,传递的方法应该是 fun (int *p )
那你要修改一个 int *,那么传递的方法自然是 fun (int **p )

struct Node *next
可以认为,*next 是一个 struct Node ,也就是一个 Node 的结构体(注意前面写的是 *next )

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/177016

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX