【问题标题】:why is there NO 'l-value required' error while incrementing array name [duplicate]为什么在增加数组名称时没有“需要左值”错误[重复]
【发布时间】:2017-12-11 07:20:26
【问题描述】:

据我所知,你不能修改数组变量

这段代码怎么运行没有任何错误。 有什么我在这里想念的吗? (这不是关于为什么会有 'L-VALUE REQUIRED' 错误,而是关于为什么没有。)

    #include<stdio.h>
int strlens(char *s);
void main(){
    char s[]="get me length of this string ";

    // s++ ; this would give 'L-VALUE REQUIRED ERROR'

    printf("%d",strlens(s));    
}
int strlens(char s[]){
    int i;
    for(i=0; *s!='\0';++i, ++s) ; //++s:  there is NO 'L-VALUE REQUIRED ERROR'
    return i;


}

【问题讨论】:

  • s 不是strlens 中的数组名,它是一个指针。数组和指针是不同的类型。
  • 当您执行char s[] 时,s 是一个名称(编译时符号;没有运行时意义)。它没有分配任何空间,因此不存储地址。如果要进行指针运算,则需要将地址存储在某个地方,不是吗?在函数参数的情况下,您的原始字符数组的地址被压入堆栈,您的函数参数s 指的是那个。因此,函数参数s 为其分配了存储地址的空间。您现在可以进行指针运算,因为您可以对其进行运算。
  • @Yashas:“数组参数表现为指针”:它们不只是“表现为指针”,它们指针。
  • 错误信息具有误导性。 smain 中的左值。它不是一个可修改的左值。
  • 明确说明这一点:int strlens(char s[]); 100% 等效int strlens(char * s); 它是可互换的,编译器应该创建 exact两者都有一些代码。

标签: c arrays lvalue


【解决方案1】:

对于经验丰富的 C 程序员来说,C 语言的一个怪癖是数组是“通过引用传递”的,但它却让新的 C 程序员无计可施。一般来说,大多数表达式中使用的数组名称会“衰减”到其第一个元素的地址。函数将这种情况带到了一个极端情况,其中函数参数中的数组语法实际上是指针类型本身的别名。

这在 c11 的 §6.7.6.3 函数声明符的第 7 段中有所描述:

将参数声明为“类型的数组”应调整为“限定指针 type'',其中类型限定符(如果有)是在 [] 的 数组类型推导。


从历史上看,这个怪癖是为了保持与 C 的前辈 B 和 BCPL 的行为兼容性以及高效的结构布局。 C 的前身具有数组的语义,因为它的物理布局实际上是一个在运行时动态分配和初始化的指针。当传递给一个过程时,指针语义是一种自然的采用。 Dennis Ritchie 发明了允许数组语法表示数组实际地址的概念,然后在传递给函数时保持指针语义。因此,C 语言的发明者认为这个怪癖是解决现实世界问题(语义兼容性)的新方法。

参考资料: The Development of the C Language

【讨论】:

  • 数组是“通过引用传递”——你的意思当然是正确的,标准引用是相关的,但我还是不想这么说方式 - C 中没有“按引用传递”,但指针允许程序员“模拟”它。因此,我会更好地说明数组只是 根本无法传递 以及表达式评估和类型调整的规则(通常一起称为“数组 decay 到指针”,其中也不在标准中)帮助“轻松”传递指针。
  • 我认为更精确的术语将通过指针/地址传递。
  • @FelixPalmen:这些引号是为了表明我没有按字面意思使用这个词。然而,“指针是引用”的概念是从 Ritchie 的论文 C 语言的发展 中传授给我的。 Decay 是我在精通 C 时学到的一个术语。我可能在 C-faq 中看到了它。它也应该用引号引起来,我已经进行了编辑。
  • 同意@FelixPalmen 数组不是通过引用传递的。如果是,则意味着更改被调用者中的“数组引用”将更改调用者中的数组。即数组实际上是内存中的不同位置。正确的一点是根本不传递数组。
  • @JeremyP:这种解释(数组实际上是内存中的不同位置)并不是对更改通过引用传递的函数参数意味着什么的通常解释。您将指针参数混为一谈,就好像指针对象本身就是通过引用传递的一样。 Dennis Ritchie 试图模拟数组的引用传递以避免堆栈负担。
【解决方案2】:

这一行

char s[]="get me length of this string ";

定义一个字符数组。 s 不是一个指针,它计算为一个地址(例如,当提供给一个指针时,或者当访问像 s[i] 这样的值时,相当于*(s+i)),或者表示数组占用的空间(例如在@ 987654325@)

但是在这样的函数签名中

int strlens(char s[]){

char s[]等价于char *s,您可以将s 视为指针。

【讨论】:

    【解决方案3】:

    char arr[] = "asds"

    在这里,arr 只是一个名称。它指的是内存位置,但不是指针。编译器直接替换使用arr 的地址。它不是指针,因为与指针不同,它没有分配任何空间来存储地址。它只是一个编译时符号。因此,在运行时没有什么可以做指针运算的。如果您必须增加某些内容,那么该内容应该在运行时存在。

    更多详情:

    基本上,文字“asds”存储在您的可执行文件中,编译器知道它的确切位置(好吧,编译器将它放在可执行文件中,所以它应该知道?)。

    标识符arr 只是该位置的名称。如,arr 不是指针,即:它不存在于存储地址的内存中


    void func(char arr[])

    在函数参数的情况下,arr 在运行时确实存在于内存中,因为参数在进行函数调用之前被压入调用堆栈。由于数组是通过引用传递的,因此实际参数的第一个元素的地址被压入调用堆栈。

    因此,arr 在堆栈上分配了一些空间,用于存储实际数组第一个元素的地址。

    现在你有了一个指针。因此,您可以递增(或对其进行任何指针运算)。

    【讨论】:

    • “像一个指针”->它一个指针,句号。
    • "它没有为它分配任何空间",因为这句话是错误的。 arr 很好地“使用”内存,即 4+1 chars.
    • @alk 我更新了它,但看起来不太好。我不知道如何把它变成一个简单的正确句子。
    • 这样更好。顺便说一句,我没有 DVed。
    猜你喜欢
    • 2012-11-19
    • 2012-10-01
    • 1970-01-01
    • 2023-03-16
    • 2014-04-07
    • 1970-01-01
    • 2011-09-07
    • 2014-11-18
    • 1970-01-01
    相关资源
    最近更新 更多