【问题标题】:We use ampersand during user input but Why we don't need this during assignment in C?我们在用户输入期间使用&符号,但为什么我们在 C 中的分配期间不需要这个?
【发布时间】:2018-05-31 18:06:05
【问题描述】:

我们在用户输入过程中使用 & 运算符。这意味着我们正在将特定值分配给特定变量的内存地址。但是在赋值过程中,我们不需要和号。在没有任何内存地址规范的情况下如何传递值?示例代码如下。

#include<stdio.h>

int main()
{
    int a, b, total;
    scanf("%d%d", &a, &b); //& for memory address
    total = a+b;           //how the value is being tranfarred to total without any memory address?   
    printf("%d", total);

    return 0;
}

【问题讨论】:

  • 我猜你首先需要查找的概念是“按值传递”和“按引用传递”。其中哪些在 C 中使用。
  • scanf 需要知道将值放在哪里,因此将需要该值的变量的地址传递给它。如果您刚刚传递了ab,那么scanf 接收这些变量的当前值(如果有,因为ab 尚未初始化),这对它没有用处。
  • 类似:a 右侧是a。左侧的totaltotalreference。对于scanf("%d%d", &amp;a, &amp;b);&amp;a 是 RHS。
  • 我一直认为scanf 是邪恶的,是对新学习者的残酷恶作剧,但这是它背信弃义的另一个例子,暗示&amp; 在某种程度上是分配任务所必需的一般。
  • @PeterJ_01:这怎么不是编程问题? OP 试图了解 C 的工作原理,这是一个相当微妙的点,与左值以及它们在不同上下文中的处理方式有关。

标签: c memory addressing


【解决方案1】:

scanf 函数必须访问正在读取值的对象。所有的 C 函数参数都是按值传递的,所以如果你写了

int a, b;
scanf("%d%d", a, b); // THIS IS WRONG

scanf 函数只能看到调用函数时的 ab 的值。无法修改a

赋值需要一个左值作为它的左操作数。左值(稍微简化)是指定对象的表达式;变量的名称是最常见的左值类型。由于赋值运算符= 内置在语言中,因此它可以有适用于它的特殊规则。特别是,= 的 LHS 上的左值标识了分配给的对象;它不会产生该对象的(以前的)值。这就是语言定义赋值的方式。

例如,如果你写:

a = b;

ab 都是左值,但由于它们的上下文,它们的处理方式不同。 a 指的是名称为a 的对象。 b 也指一个对象,但在这种情况下,它会产生该对象的当前值。

如果赋值是作为函数调用实现的,则必须采用被赋值对象的地址:

int assign(int *lhs, int rhs);
assign(&a, b);

语言可以要求分配的 LHS 的地址,但只允许目标对象的名称要方便得多。

(为了解释上面的简化,当前标准对 lvalue 的定义是“可能指定对象的表达式(具有除 void 之外的对象类型)”。“可能”一词在那里因为,例如,*ptr 其中ptr 是一个指针是一个左值,即使ptr 的当前值是NULL。作为一个左值是一个编译时概念。尝试将一个值分配给@987654340 @ 是合法的,但具有未定义的行为。)

【讨论】:

  • 添加一些东西scanf("%d%d", a, b); // THIS IS WRONG 它将假定ab 是变量的地址(va_arg 将尝试将它们转换为指针)并尝试取消引用它们或溢出它们(如果 %s 是预期的字符串。这都会导致未定义的行为。但是如果 ab 都已初始化,我认为标准函数保证对 NULL 进行检查,然后什么都不会发生.
  • @Edenia:没有演员表发生。 scanf假设参数是int* 类型。传递错误类型的参数具有未定义的行为。传递正确类型的空指针具有未定义的行为。初始化 ab 不会改变这一点。
  • 可变参数函数使用 va_arg(list, type) (因为类型未知),其中类型必须是正确的可解引用类型。
  • printf-alike 函数倾向于检测提供的 NULL 指针(例如,printf("%s\n", NULL); 可能是善良的并打印“(null)”,但你是对的 scanf 没有。已经有一段时间了我上次将​​ NULL 传递给它。或者其他标准函数。
  • @Edenia 我想scanf 家庭在给定NULL 指针并返回0 时也可能很友善,但C 不是一种非常友善的语言。
【解决方案2】:

变量是一个抽象单元,具有与之关联的地址。 使用 int var 等语法声明的变量表示位于特定地址 &amp;var 上的值。

scanf 期望指针指向变量,即地址,这就是您使用 & 符号的原因。这是必需的,因为当您将变量传递给函数(没有与号)时,您只传递它们的值,而不是实际变量的地址信息。 有人喜欢说,当您将变量传递给函数时,函数会“复制”变量的

如果你想 decalre 指针(表示其特定类型的地址的变量,而不是值),你可以这样做: int* var; 这个变量将用于尝试访问另一个int 类型的变量,方法是为其分配地址。 int* var = &amp;some_int

以下面的场景为例。您想要创建一个函数,将一些值存储在传递给它的变量中。为此,您必须像这样定义您的函数:

void foo (int* var)
{
*var = 1; // * is the de-reference operator which shows the value of the address `var` and we assign the value to 1.
}

【讨论】:

  • register 变量怎么样。它们是可寻址的吗?你对他们有什么看法。
  • 好吧,我认为在 C 中,您不能使用寄存器存储来获取变量的地址。正如 C 标准在 Cf 中声称的那样。 C11 6.7.1/6:但是寄存器是存储类型修饰符,不知道是不是问题。
【解决方案3】:

赋值是一种 C 语言结构。

C 知道赋值不能使用其左侧操作数的值,而是使用其地址*。你不需要明确地说&amp;

对于用户定义的函数,尤其是无原型的旧式函数(未指定的参数类型)和像scanf 这样的可变参数函数,C 无法知道他们想要什么——他们可能想要地址,或者他们可能同样想要值,因此您必须通过添加或不添加&符号来指定它。

现在有了一个原型函数,例如:

void take_address(int *x);

理论上你可以扩展语言,以便

void use_take_address(void){ int x=42;  take_address(x); }

会隐式获取参数的地址(毕竟 C++ 对引用做了类似的事情),但可以说,它会损害 可读性。

有了&amp;x,你可以确定这个函数至少理论上可以修改x;如果没有&amp;,您可以或多或少地确定它不能(除非x 是一个数组)。

这是一种语言设计选择。


*这实际上不是 100% 准确的。赋值也可以使用没有地址的基于寄存器的变量,并且在 C 中对变量使用 &amp; 实际上会使其无法使用 register 显式标记,但这些细节是好的优化器可以做到的闯过。

【讨论】:

  • 你真快。
  • @Edenia 我对此一无所知:D 我现在正在使用一个慢速互联网连接。
  • 那你打字的速度想的快哈哈,你想的也快。
猜你喜欢
  • 2020-06-22
  • 2010-11-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-15
  • 2011-04-14
  • 2023-03-07
相关资源
最近更新 更多