【问题标题】:Is passing pointer by value or by reference the same是按值传递指针还是按引用传递相同
【发布时间】:2022-01-07 19:08:34
【问题描述】:

在 C 中通过引用传递指针和通过值传递指针有什么区别?

我的理解是,当您将参数传递给方法时,会创建一个新的堆栈框架,并且这些值会被复制到不同的内存地址,除非通过引用传递。如果通过引用传递内存地址。

使用指针时,我注意到如果我按值传递 char* 并在返回主堆栈帧时在不同的堆栈帧中对其进行修改,则 ptr 的值已被修改。

我写了简短的代码来说明我在说什么。

//test pointer ref
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void passbyval(char const *lit,char* str){
    printf("---passbyval---\n");
    printf("%s\t%p\n",lit,&lit);
    //modify string
    strncat(&str[2],"/",1);
    printf("%s\t%p\n",str, &str);
}
void passbyref(char const **lit, char** str){
    printf("---passbyref---\n");
    printf("%s\t%p\n",*lit,&*lit);
    //modify string 
    strncat(&(*str)[1],"/",1);
    printf("%s\t%p\n",*str,&*str);
}
int main(){

    char const *litstr = "hello this is a test";
    char *str = (char*)malloc(sizeof(char)*100);
    scanf("%[^\n]",str);
    printf("---main---\n");
    //print original value and address
    printf("%s\t%p\n",litstr,&litstr);
    printf("%s\t%p\n",str,&str);
    passbyval(litstr,str);
    //modified value and address from pass by value 
    printf("\nretfromval:%s\t%p\n",str,&str);
    passbyref(&litstr,&str);
    //modified value and address from pass by ref
    printf("\nretfromref:%s\t%p\n",str,&str);
    free(str);
    return EXIT_SUCCESS;
}

输出

不通过引用传递char* 你想在 void 方法中修改是个好习惯吗?

如果指针引用的值是通过引用隐式传递的,为什么我会使用通过引用传递指针。

也许我遗漏了一些东西可以更好地解释一下吗?

【问题讨论】:

  • 假设你有一个函数接收一个指针并且可以修改这个指针。通过引用将指针传递给函数不是完全合理吗?
  • C中没有“引用传递”,只有值传递。
  • @WilliamPursell:在 C++ 采用术语“引用”来表示语言的内置特性之前,它在 C 的上下文中用于表示手动传递指针。事实上,它仍然以这种方式使用,并且 C 标准说指针提供对对象的引用。当有人说,在 C 的上下文中,某些东西是通过引用传递的,他们的意思是源代码传递了那个东西的地址。过去和现在在 C 语境中对英语的使用在 C++ 开发采用该术语作为其自身用于不同目的时并没有改变
  • @Eric:你说的一切都是真的,但 OP 似乎确实混合了 C 和 C++ 的术语。我认为他的预期问题的答案是,仅当要修改指针本身时,才需要通过地址/引用传递指针。如果只想修改指针的目标,按值传递指针就可以了。
  • @chux-ReinstateMonica 我不得不参考上面 Eric 的原始评论。我正在尝试将指针而不是指针的副本手动传递给“子例程”堆栈帧。本质上,当我说在 c 的上下文中通过引用传递时,我是在传递一个指向指针的指针。

标签: c pointers pass-by-reference pass-by-value


【解决方案1】:

使用指针时,我注意到如果我按值传递 char* 并在返回主堆栈帧时在不同的堆栈帧中对其进行修改,则 ptr 的值已被修改。

您的示例都没有这样做。此外,您的任何代码都不会打印litstrstr 的值。要打印指针的值,请删除所有 printf 调用中的 &amp;。然后你会看到调用例程和被调用例程中指针的值是相同的。

mainprintf("%s\t%p\n",litstr,&amp;litstr); 打印:

  • 在内存中以litstr 的值地址开始的字符串(因为%slitstr)和
  • litstr 的地址(不是值)(因为%p&amp;litstr)。

同样,printf("%s\t%p\n",str,&amp;str); 打印str 处的字符串和str 的地址。

passbyval 中,printf("%s\t%p\n",lit,&amp;lit); 打印lit 处的字符串和lit 的地址。由于litpassbyval的参数,所以它有自己的地址,与litstr的地址不同。如果您打印了litstrlit 的值,而不是它们的地址,您会发现它们是相同的。

同样,printf("%s\t%p\n",str, &amp;str); 打印str 处的字符串和str 的地址。 passbyval中的参数str的地址与main中的本地对象str的地址不同,但它们的值是一样的。

passbyref 中,printf("%s\t%p\n",*lit,&amp;*lit); 打印lit 处的字符串和*lit 的地址。由于litmainlitstr的地址,*lit就是litstr,所以&amp;*lit就是litstr的地址。 litstr 的值将是 *lit

同样printf("%s\t%p\n",*str,&amp;*str);打印*str处的字符串和*str的地址,也就是mainstr的地址。

【讨论】:

  • 我不确定我是否理解正确,但我故意打印出字符串的值和地址...如果我错了,请纠正我,但 & 指定了 char(BYTE) 的偏移地址我指的是字符串,当您打印 %s 时,它会打印字符串缓冲区的每个 BYTE,直到遇到“\0”。所以我故意添加了它们。如果您查看输出,您将看到指针地址在您通过 ref 时保持不变,并在您通过值时更改。我的意图是显示指针地址发生变化,但返回到主堆栈的值保持不变。
  • 保持不变是指方法中操作的值返回到主堆栈帧
  • @KyleC:char const *litstr = "hello this is a test"; 之后,litstr 不是字符串。它是一个char *,它是一个指向char 的指针。所以litstr 是一个指针。它指向字符串的第一个字符。 &amp;litstr 是指针的地址。不是字符串的地址。
  • 当你传递一个 char const (*ptr) 它仍然是一个 char const *ptr,它不会衰减为一个 char ptr,如果你试图修改这个字符串,你会出现总线错误。正确的演示是显示当您通过“按值传递指针地址”(我称之为引用,但 C 仅按值传递)时,指针地址已更改,而不是它指向的 ptr 的值。我用我的输出清楚地证明了这一点。谢谢!
【解决方案2】:

在C中通过引用传递指针和通过值传递指针有什么区别?

在C中没有通过引用传递指针,所有的变量都是按值传递的,甚至是指针。

我的理解是,当您将参数传递给方法时,会创建一个新的堆栈框架,并且这些值会被复制到不同的内存地址,除非通过引用传递。如果通过引用传递内存地址。

同样,指针不是通过引用传递的,而是传递了存储在指针中的值的副本,它指向的地址,您可以通过更改函数内部的指针,并检查它如何反映在原始指针上,剧透,它没有。

在使用指针时,我注意到如果我按值传递一个字符,并在我返回主堆栈帧时在不同的堆栈帧中对其进行修改,则 ptr 的值已被修改。 *

您传递的是一个地址,一个存储一些数据的内存位置,当您更改存储在该内存地址中的数据时,它将是永久的,无论您在哪里做,实际上这是优点之一使用指针,以便您在声明的范围之外更改某些变量的内容。

【讨论】:

  • 我当然同意你所说的一切。当我通过引用说时,我想您知道我的意思,但为了清楚起见,我指的是该指针的内存地址被复制到子例程堆栈帧中。谢谢!
  • @KyleC 我明白了,我只是想强调一下,在 C 中,一切都是按值传递的,我看到您已经对这些事情有了一定的了解,不要把我的回答当成批评,在大多数情况下案例术语很重要。
  • 别担心,我接受任何有用的建议/批评,所以感谢您的意见。只是为了总结其在 c 编程中的标准/良好实践,以在不传递原始地址的情况下修改方法中的字符串?谢谢!
  • @KyleC 如果你传递一个可修改的字符串,一个可修改的空终止字符数组,你将传递原始字符串,一个指向它的指针,所以如果你修改它,它会修改原件,但你可以复制它,在你打算修改它的函数内部而不修改原件。另一方面,如果你传递一个指向字符串文字的指针,那么修改是不安全的,你不应该这样做。
【解决方案3】:

您需要了解指针和数组的工作原理。

pointer 是一个独立的对象,有自己的引用,持有底层对象的引用。

char *p = "aaa";

print("%p\n", (void *)p); //prints the reference of the string literal "aaa"
print("%p\n", (void *)&p); //prints the reference of the pointer `p`

数组是连续的内存块。数组衰减到指针。这些指针在内存中没有物理表示,只是对数组第一个元素的引用。

char p[] = "aaa";

print("%p\n", (void *)p);  //reference of the first element 
                           //of the array p the type is `char *` 
                           //(pointer to char)
print("%p\n", (void *)&p); //reference of the first element of the 
                           //array p the type is `char (*)[4]` 
                           //(pointer to an array of 4 char elements)

【讨论】:

  • 呃什么...我想我理解堆内存和堆栈内存之间的区别。谢谢!
  • 我没有写任何关于堆和栈的东西
  • 我也没有写任何关于数组和指针的区别。谢谢!
【解决方案4】:

在 C 中通过引用传递指针和通过值传递指针有什么区别。

在 C 中,参数在传递给函数之前有多种预处理方式。

  • 不处理
    将参数的副本提供给函数。 char *s = ...; puts(s);puts() 获得了s 的副本。

  • 通常的论据推广
    在将副本提供给函数之前,该参数被提升为int, unsigned, double, ...float fl = 12.5f; printf("%g", fl); printf() 获得了double 12.5 的副本。

  • 函数转换为地址
    对于tss_create(tss_t *key, tss_dtor_t dtor);tss_dtor_t 是函数指针类型void (*)(void*)tss_create(... , foo) 不接受函数 foo() 并传递它。而是传递函数地址的副本。

  • 数组转换为其第一个元素的地址
    使用char s[] = "Hello; puts(s);,将&amp;s[0] 的副本传递给puts()

现在,这些是否像 OP 的 pass by reference

是的。

对于char *end[1]; strtod("123.4", end);从调用者的角度来看end 在其中整个strtod() 调整,因此行为和闻起来像“路过”参考”来自呼叫者的 POV。这在技术上仍然是“传递副本”,但副本是参数的第一个元素的地址。

除此之外,我没有看到类似的“通过引用传递指针并在 C 中通过值传递指针”来找到差异。

【讨论】:

    猜你喜欢
    • 2016-11-28
    • 2017-11-14
    相关资源
    最近更新 更多