在C语言中,为什么调用函数通过指向被调用函数的指针传递数组,而同一个调用函数会通过值传递基元(例如整数)给同一个被调用函数?为什么不通过指针同时传递数组和基元呢?

您所在的位置:网站首页 指向数组的指针是什么字母 在C语言中,为什么调用函数通过指向被调用函数的指针传递数组,而同一个调用函数会通过值传递基元(例如整数)给同一个被调用函数?为什么不通过指针同时传递数组和基元呢?

在C语言中,为什么调用函数通过指向被调用函数的指针传递数组,而同一个调用函数会通过值传递基元(例如整数)给同一个被调用函数?为什么不通过指针同时传递数组和基元呢?

2023-05-14 04:05| 来源: 网络整理| 查看: 265

推荐答案1

C语言 仅 按值传递参数. 任何 第一类对象可以按值传递.但是,实际上比这更简单,因为该功能几乎与它无关.

为了简单起见,让我们忽略varargs暂时的功能,并假设我们我们在范围中具有原型.

函数参数实际上是自动变量,该变量由函数参数列表初始化.

考虑考虑此片段:

void foo(int x) {  ... }  int main() {  foo(42); } 

42充当 x foo() 的初始化器.它与…

int x = 42; 

没有太大区别. p>

在语义上,函数参数只是另一个范围中变量的变量初始化.

返回值相似:您声明的返回类型,您的返回值,返回值是什么将这种类型的胁迫(好像是通过分配)被强制为未命名的临时性.然后,该未命名的临时变量可用于任何包含功能调用的表达式.而且,像函数参数一样,您不能以几乎无法通过数组的方式返回数组.

这意味着您最终都会得到同一集合对可变初始化器的函数参数行为的行为.但是,有一个语法怪异,我将在片刻之内完成.

int a1[4] = {1, 2, 3, 4}; int a2[4] = a1; // ERROR. int *a3 = a1; // Ok. ... void foo(int *a4); foo(a1); // Similar to a3 = a1 above. 

关于语法quirk:下面的所有四个声明都意味着完全相同的事情:跨度>

void foo(int *a4); void foo(int a4[]); // Misleading. void foo(int a4[4]); // Misleading. void foo(int a4[100000]); // Misleading. 

这是不幸的混乱来源.

这里的主要问题是阵列不是一流的对象在C中,您不能将它们视为价值观.在大多数情况下,* c默默地将数组类型的表达式转换为指针,转换为数组的第一个元素.

(* 不包括 sizeof() 和地址 & . )

这绝对具有 没有什么 与函数调用有关.

等等,这纯粹是一个方面键入,C编译器默默地将其转换为指向数组的第一个元素的指向元素的表达式.

完全相同 尝试在 中尝试执行其他任何 表达式的其他任何表达式,除了我上面突出显示的特殊情况外.

除了我上面标记的误导性声明之外,功能调用不会输入图片. C函数调用的行为(忽略Varargs)与其行为与分配和可变初始化一致.

如果您不头语,请考虑此示例:

#include   struct Char80 {  char line[80]; };  struct Char80 foo(struct Char80 line) {  for (int i = 0, c; i < 80 && (c = line[i]) != 0; ++i) {  if (isalpha(c)) {  line[i] = isupper(c) ? tolower(c) : toupper(c);  }  }  return line; } 

此功能接收80个字符数组的A 复制 ,并在字母字符上切换上/下情况. struct 不是原始类型.但是,它是一个头等对象,并按值传递.

,您甚至可以做

struct Char80 a = { // ... }; struct Char80 b; b = a; 

y tho?

c和c ++具有这种怪异的行为,可以从 B programming language. 特别是b和c处理数组的阵列非常不同.但是,丹尼斯·里奇(Dennis Ritchie)使他们看起来相当相似.要在将代码从B移到C时捕获错误,Ritchie想要在b和c.

之间的含义上进行艰难的突破,因此,C数组的怪异定义服务于这个目的.

不幸的是,我认为现在根本不需要,而且我怀疑这是30年前经常需要的.高峰迁移可能接近40年前.

,但是,鉴于许多其他事物,可能没有一种干净的方法来迁移到当地的最低限度.掉入其中,例如:

c仅提供逐个价值. t具有本机字符串类型;相反,它在未指定长度的字符阵列上覆盖了字符串.由此产生的成语加强了此 状态 . c的类型系统无法表达"未指定的长度",除了指向其第一个元素的指针外>没有办法表达"未指定长度的数组",将帕斯卡烤制数组长度纳入该类型,而无需" un-span> 请参阅 Why Pascal is Not My Favorite Programming Language 有关详细信息. 更实用的pascal实现提供了一种"魔术"字符串类型,该字符串类型在核心语言定义之外围绕着它. c也定义了按照指针算术的数组索引,因此很容易假装指针代表了一个数组.与我所经历的其他高级语言相比,这是C(由C ++继承)的一个异常独特的设计决定.

等等,其中C最终是一种务实的,即使时髦的局部最大设计最大,如果不是不可能的话,这是相当困难的. -gray" width =" 148px"样式="盒子尺寸:边框框;位置:相对;边距:2em自动; padding:0px; width:148px; height; height; 2px; 2px; border:0px;>

and varargs(又称variadic参数)?

某些功能允许您传递可变数量的参数.俗语,这些被称为" varargs函数",或更正式, variadic functions.

variadic函数允许您将可变数量的参数传递给函数的未命名的未型参数. printf() 及其函数是variadic函数的海报儿童.

c标准指定variadic参数体验隐式 default argument promotions. 例如, char 和 short 获取促进到 int 和 float 被提升为 double .有关确切详细信息,请参见上面的链接.

推荐答案2

c是从较早的语言中得出的(是的). p> auto a[N]; 

搁置了一个额外的单词,以存储第一个元素的偏移(本质上是指针):

+–––+ a: | | ––––––––––+  +–––+ |  ... |  +–––+ |  | | a[0]

不幸的是,这意味着在大多数情况下,数组在大多数情况下都会丢失其"数组" - 当您调用以数组表达式作为参数的函数时,例如

foo( a ); 

编译器用指针表达式替换 a ,因此有效地将函数称为

foo( &a[0] ); 

因此,该函数实际收到的是指针值,而不是数组.

此行为是数组独有的 - 其他聚合类型,例如结构和工会,具有这种"衰减"行为.

c c通过值通过所有函数参数,只是在数组的情况下,值是由评估数组表达式产生的指针.

脚注

[1] Chistory推荐答案3

简单地,"呼叫" vs .创建语言时,"逐一引用"是一种选择.请记住,C是B的孩子,只有一种数据类型,一个"单词".因此,"按价值呼叫"是完全合理的,特别是因为呼叫功能获得了变量的私人副本(在呼叫堆栈上).当Dennis开始修改B以创建" NewB"(最终成为C)时,他在添加了少量数据类型时保持了这一约定.正如其他答案所建议的那样,诸如Fortran之类的其他语言使用"引用呼叫",而Algol家族(例如Pascal和Algol-X)都可以根据如何声明该功能来进行.

另外,请记住,C是为PDP-11创建的,而硬件呼叫堆栈仅支持完整单词的推送和弹出操作.因此,丹尼斯的方案非常方便且高效.

您注意到,丹尼斯(Dennis)使用逐次引用来阵列.阵列和结构未放在呼叫堆上.因此,丹尼斯所做的是,由于指针使用"逐个通话"(就像其他面向单词的数据类型一样),因此他创建了一个语法,以在程序员想要通过时,将指针(参考)创建为数组相同.

btw:您没有问,但是C c通过8位字符作为呼叫堆栈上的整数是从相同的遗产和PDP中保留的. -11硬件.

推荐答案4

这是一个设计决定.通过值传递普通数据,避免需要创建本地副本,并且仍然可以通过参考指针明确地通过.

某些语言像fortran一样,始终传递给fortran,这使您成为您的行列

c在当时的工作似乎很方便,

推荐答案5

从一个示例开始:

int Function( char Array[100] ); 

如果您服用 sizeof(Array) 您将获得 .

我将首先从ABI-应用程序二进制接口或调用惯例开始. CPU制造商制造的标准定义了参数如何传递到函数. 现在,没有ABI将是烂摊子. A0>

和类似的其他CPU.因此,如果类型的大小大于8个字节,则必须将其作为参考(指针)传输.

现在让我们看看CPU如何与数组一起工作,例如,在上述功能中,我们有代码:

char Ch = Array[20]; 

编译器生成的代码将使用寄存器间接地址,类似于:

MOV AL, [SP+20] 

SP是堆栈指针.

原因函数参数是在CPU寄存器中传输的(快速调用惯例),因此无法传输数组.好吧,可能会传输较小的元素,但访问元素变得非常棘手.例如,如果我们具有 char Array[4] 可以在寄存器中传输,但是访问元素3将是: (Register >> 24) & 0xFF .

让我们以较大的结构为例.如果按值转移,则编译器将在堆栈上制作副本,并且只需将堆栈上的struct Copy上的指针转移到函数中即可.阵列也是可能的,但是阵列可能很大,制作复制非常不切实际.更不用说堆栈是非常有限的资源(例如,在Windows默认堆栈上是1MB). SO,C/C ++决定不按值传输数组.

as通过参考转移,数组地址将起作用,并且将非常快 - 注册间接地址(假设CPU支持字节/word/dword操作).

上面的示例中还有另一个问题 - 由于数组是通过指针信息传输的,有关数组大小的信息丢失了.我提到了如何返回sizeof(void*).

,但是如果您想按值传输数组,则只需将其放入struct and thruct struct and by value -compiler -Compiler将使在堆栈上复制.请小心,编译器优化可能会在堆栈上删除复制.



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3