我在C中乱搞,并且认为尝试将argv的类型从char *更改为int很酷,只是为了看看会发生什么。我写了这个:
#include <stdlib.h>
#include <stdio.h>
int main(int argc, int argv )
{
printf("arg is %d \n", argv);
}
我从这个程序得到了非常奇怪的输出。每当我运行它时,无论我运行什么参数,它似乎只是向我吐出随机数。这是输出:
[14:30:00][maksim]~/learnProg/cDance$ ./dink
arg is -2058142376
[14:30:01][maksim]~/learnProg/cDance$ ./dink 2141
arg is 2111473256
[14:30:04][maksim]~/learnProg/cDance$ ./dink 2141
arg is -8005928
(该程序称为丁克)。这是怎么回事? C编译时做了什么?如果我使用除int之外的数据类型,如double或者结构或其他什么会发生什么?
答案 0 :(得分:4)
正如其他人所说,行为未定义(因此可能发生任何事情)。
让我们来看看三个&#34;典型&#34;虽然行为。传递参数的三种常用方法是:
Intel x86系统大多使用第一种方法(但有时是第二种或第三种方法)。基于MIPS的处理器大多使用第二种处理器。
如果系统使用一个或多个堆栈,通常的调用方法是:
main
的例程),推送参数,通常从右到左,即以相反的顺序。堆栈通常(但不总是)在C中看起来像*--sp = value;
,堆栈指针从某个高地址开始下降。main
)sp[0]
,sp[1]
等地址。如果调用机制使用相同的堆栈作为参数传递机制,则索引可以从1或2开始甚至更多(例如sp[2]
是第一个参数,sp[3]
是第二个参数。在这种情况下,argc
可能会正确显示,但argv
会错误地解释调用者所推送的内容,从而产生一种奇怪的int
。如果底层系统足够花哨(检查类型),它可能会检测到调用者推送了char **
类型的值,但您正在访问int
类型的值,并为您提供某种类型的运行时错误。尽管如此,大多数系统都希望尽快给出错误的答案,但不要使用类型检查。所以你会得到一个看起来很奇怪的int
,但它实际上是基于调用者试图通过的实际指针值(至少部分见下文)。
如果系统使用通用寄存器(代替或使用使用GPR的堆栈系统之前,如果使用许多参数,则通常会回退到堆栈上,有时将它们用于所有可变参数函数,即使用<stdarg.h>
设施),然后调用方法看起来更像这样:
int argc
值和char **argv
值)移动到前两个参数寄存器中(例如,SPARC上的%o0
和%o1
,或{{ MIPS上的1}}和$a0
)。 在这种情况下,代码通常与基于堆栈的系统上的行为相同。它运行得更快,因为寄存器中的参数往往比内存中的参数需要更少的CPU周期。 (这就是为什么有些英特尔编译器有时会在寄存器中传递一两个参数。)
如果系统使用专用寄存器,我们会得到一个新的明显行为。让我们说浮点值进入$a1
寄存器(某些SPARC系统是真的; x86代替MMX和SSE寄存器);指针值进入f
寄存器(a 680x0 CPU);整数值进入a
寄存器(680x0,再次 - 尽管实际上大多数680x0系统只使用&#34;堆栈&#34;但是让我们假设我们有一个使用寄存器)。这一次,调用d
的东西需要传递一个整数main
和一个指针argc
,所以它会这样做:
argv
移动到数据寄存器argc
d0
移动到指针寄存器argv
a0
现在,在main
中,您告诉编译器期望两个整数参数,它们分别到达寄存器main()
和d0
。 CPU注册d1
中的内容是什么?谁知道,调用d1
之前没有在调用之前设置它。它拥有它拥有的任何价值,无论是谁最后在其中获得了一些价值。该值不再与预期的main
相关联,因为它位于注册argv
中。
现在,即使你有一个基于堆栈或基于GPR的呼叫系统,还需要考虑另外几个问题:
a0
s只有32位怎么办?在这种情况下,调用者按下64位值,或将64位值写入参数寄存器;但int
只看32位。你会看到实际给出的一半。main
是64位怎么办?这是一个不寻常的实现,当然,但现在你要查看只提供32的值的所有64位。&#34; extra&#34; 32位可能全为零(这对于GPR中的参数是典型的),或者可能是某些无关值的32位,类似于int
时检查寄存器d1
的情况。来电者填写了注册号main
。还有一个值得注意的可能性。如果您构建类似的C ++代码(使用a0
以外的函数),它通常无法链接。原因是C ++编译器经常使用一种名为&#34; name mangling&#34;处理重载的函数。名为main
的函数接受一个f
和一个int
参数并返回char **
,生成链接时符号int
。名为Z1fiPPC
的函数需要两个f
并返回int
,而是生成链接时符号int
。我没有看到C编译器这样做,但他们可以做到这一点。在这种情况下,编译器会在链接时检查您的程序是否定义了Z1fii
- Z4mainippC
- 如果是,则在提供这些参数的调用者中进行链接;或者它会检查int main(int, char **)
- Z4mainv
- 并且在那种情况下,调用者中的链接不提供任何参数。如果找不到任何函数,链接器可能会检测到您写了不正确的int main(void)
并且根本没有生成可执行文件!
答案 1 :(得分:3)
您将获得未定义的行为,这意味着任何发生是合法的。 main
必须声明为:
int main(void)
或作为:
int main(int arg, char** argv)
或您的实施所指定的某种形式。
从ISO C99标准的J.2节开始:
在以下情况下,行为未定义:
...
- 托管环境中的程序未使用其中一个指定的表单(5.1.2.2.1)定义名为
main
的函数。
答案 2 :(得分:2)
argv
作为指向字符串指针数组的指针传递给您的程序。
如果说谎并告诉编译器它是int
,指针的字节将被解释为int
,你将获得一个内存地址。 (在64位系统上,你可能会崩溃)
如果假装它是float
,编译器可能会将这些字节/位解释为IEE-754编码的浮点值,从而产生不同的奇怪数字。 (究竟发生了什么取决于调用约定)
如果你假装它的任何类型与指针的宽度不同,你可能会崩溃。
C完全按照你所说的去做。由你来告诉它如何解释事物。
答案 3 :(得分:0)
嗯...
Argv是一个数组。在C中,数组只是指针。指针内部只是内存位置的整数。所以,你看到的数字是内存中的位置。 (我猜这些否定因为它不是无符号的)
答案 4 :(得分:0)
C main()函数接收参数计数的整数和指向char数组的指针。
您的输出只是该指针包含的内存地址。 如果将其强制转换为其他变量类型,它们也将包含“垃圾”。
在正常情况下,如果可能的话,应该避免使用指针。
答案 5 :(得分:0)
让我们首先了解argv
究竟是什么。
考虑标准main()
格式。这是int main(int argc, char *argv[])
这里argv
是一个字符指针数组。由于数组的名称是指向它的第一个成员的常量指针,我们会说argv
是指向它的第一个成员的指针。即argv
是指向字符指针的指针。
现在请注意名字在这里无关紧要。它可以是argv
旁边的任何内容。重要的是main()
的第二个参数是指向字符指针的指针。即第二个参数是指向字符的指针。
因此,当程序开始执行时,内存地址作为第二个参数传递给main()
,这是另一个指针的地址。而'另一个'指针是第一个参数的第一个字符的内存地址。而这个论点恰好是程序的名称。
因此,当您说int main(int argc, int argv )
时,您正在使用int
值投射地址。如果sizeof(int) == sizeof(int *)
那么那根本不是问题。在这种情况下,该值不会降级。
现在,当您说printf("arg is %d \n", argv);
时,您只是打印该地址。而已!无论你给一个程序的参数是什么,地址都是一个随机值。这就是为什么你得到随机的no.s,它实际上是argv
数组的第一个成员的地址。即没有。 print是程序名称的地址,而程序名称的地址又是它的第一个char的地址。 (因为程序名称又是一个数组,因此它是指向它的第一个成员的常量指针。即第一个字符)
要验证这一点,请将此行添加到您的代码段:
printf("%c\n", **(char **)argv);
你会看到.
正在打印,这确实是第一个参数的第一个字符./dink
答案 6 :(得分:0)
我并不反对大多数其他答案,正如jamesdlin所说,如果main
未正确声明,C99将行为指定为未定义。我想那时你的问题就变成了这个所谓的未定义行为。我说“所谓的未定义行为”,因为它实际上非常精确地定义为平台/系统应用程序二进制接口(ABI)的一部分。虽然ABI可能没有专门解决您将指针作为int
传递的情况,但它确实定义了参数的传递方式,因此一些研究将准确揭示您的特定情况中发生的情况。
由于ABI回答了所有关于“如果我将其作为int,double或结构传递会发生什么”的问题,那么您的下一个问题可能是“我的系统的ABI是什么”。 ABI是特定于系统/平台的,它可能在Windows和Linux之间,PowerPC和X86之间,不同编译器之间,甚至不同版本的编译器之间有所不同。您没有提供必要的平台/系统信息来回答“哪个ABI”问题,但是,即使您提供了它,我也无意回答它,因为我需要进行研究(我不是专家) 。此外,这是您的实验,因此您将很好地学习和理解系统的ABI。
有很多好的信息,包括提问what is the ABI的问题,Linux ABI的简要概述,当然还有wikipedia page。 ABI问题提供了System V ABI PDF的链接,很可能涵盖了您的系统ABI,因此可能是最好的起点。
总而言之,您的实验根据C99导致未定义的行为,但实际行为由系统ABI定义,但系统ABI是系统特定的。换句话说,C99没有指定实验中的行为,因为它是C99之外的系统特定行为。另一方面,系统特定的ABI将行为定义为参数传递定义的一部分。通过了解系统的ABI,您将能够理解(即定义)您所看到的行为。很可能这个定义有点不起眼,例如,int
参数和指针参数不兼容,因此您收到的int
参数确实是随机垃圾,恰好位于某个寄存器或记忆位置。或者它可能是64位指针的上部或下部32位。