【问题标题】:Why does using char agrv instead of char **argv as the argument of main cause the following output?为什么使用 char agrv 而不是 char **argv 作为 main 的参数会导致以下输出?
【发布时间】:2015-09-06 09:56:13
【问题描述】:

当我这样做时:

int main(int agrc, char argv)
{
    printf("%d", argv);
    return 0;
}

当我从命令行运行程序时得到这个输入:

$ prog_name 0
0

$ prog_name (from 0-7 characters)
48

$ prog_name 12345678
56

$ prog_name 1234567812345678
64

// and so on...

那么这些值是从哪里来的,为什么它们会增加 8?

当我有这个时会发生什么:

int main(int agrc, char argv[])

?

【问题讨论】:

  • 这不是main 的有效签名,因此结果是未定义的行为。
  • 运算符不仅仅是为了装饰或者代码看起来更神秘。阅读一本关于 C 编程的书会清楚地回答这个问题。
  • @Olaf 是的,我了解这些操作员的工作,但我不明白为什么我会得到该输出。正如 Oblivious 船长指出的那样,我现在明白这种行为是不可预测的。我以为我会在使用 gcc 时遇到某种错误,但我只收到了警告。
  • @Olaf 了解这些运算符和了解忽略对main 提出的要求的结果是两件不同的事情。
  • @LightnessRacesinOrbit,Capt'n:我撤回了我的评论,因为它没有明确说明这是我的观点。我实际上认为了解指针/数组,并且在 UB 中使用错误的参数类型是至关重要的用 C 编程的知识。这是我告诉我的学生的第一件事。但这就是我,其他导师或资源可能不太重视这些问题。

标签: c++ c command-line-arguments main


【解决方案1】:

您的输出可能是“普通”argv 参数的地址,即隐式转换 解释为see comment belowchar。换句话说,我怀疑你所拥有的相当于:

int main(int agrc, char **argv)
{
    printf("%d", (char) argv);
    return 0;
}

在我的机器上(CentOS 6 32位)反汇编的目标代码如下:

   0x080483c4 <+0>: push   %ebp
   0x080483c5 <+1>: mov    %esp,%ebp
   0x080483c7 <+3>: and    $0xfffffff0,%esp
   0x080483ca <+6>: sub    $0x10,%esp
   0x080483cd <+9>: mov    0xc(%ebp),%eax
   0x080483d0 <+12>:    movsbl %al,%eax
   0x080483d3 <+15>:    mov    %eax,0x4(%esp)
   0x080483d7 <+19>:    movl   $0x80484b4,(%esp)
   0x080483de <+26>:    call   0x80482f4 <printf@plt>

以及您发布的原始代码:

   0x080483c4 <+0>: push   %ebp
   0x080483c5 <+1>: mov    %esp,%ebp
   0x080483c7 <+3>: and    $0xfffffff0,%esp
   0x080483ca <+6>: sub    $0x20,%esp
   0x080483cd <+9>: mov    0xc(%ebp),%eax
   0x080483d0 <+12>:    mov    %al,0x1c(%esp)
   0x080483d4 <+16>:    movsbl 0x1c(%esp),%eax
   0x080483d9 <+21>:    mov    %eax,0x4(%esp)
   0x080483dd <+25>:    movl   $0x80484b4,(%esp)
   0x080483e4 <+32>:    call   0x80482f4 <printf@plt>

在这两种情况下,$0x80484b4"%d" 格式说明符存储为字符串文字,0xc(%ebp) 负责printf() 使用的实际值:

(gdb) x/db 0xbffff324
0xbffff324: -60
(gdb) p $al
$3 = -60

请注意AL(一个字节累加器,即EAX 的一部分)仅“获取”$ebp+0xc 地址处的第一个字节(我的 CPU 是小端序,所以它实际上是 LSB)。这意味着(char) 转换会“切断”argv 地址。

因此,您可能会观察到这些数字中的每一个都未设置log2(n) 最低有效位。这是由于指针类型对象的对齐要求。通常用于 32 位 x86 机器 alignof(char **) == 4

正如在 cmets 中已经指出的那样,您违反了 C 标准,因此这是 UB 的一个示例。

【讨论】:

  • 我会认为它更有可能是按字节重新解释,而不是逻辑转换?这可能特别重要,因为 sizeof(char**)sizeof(char) 不太可能相同。
  • @LightnessRacesinOrbit:我通过使用gdb 会话检查各种输入来检查它,但我不是 100% 确定。这都是依赖于实现的。
  • 当然是依赖于实现的。这是一个关于实现在做什么的问题。 (OP 应该指定哪个 impl)
  • 对于 OP 的代码,我得到:-12100-124-28-1084-76,@9876545349@,@986 , -12, -44, 20, 52 在多次运行中使用./a.out 12345678 执行相同的二进制文件。我的平台是 Linux(基于 Linux 源代码树 3.19 的自定义内核构建)并使用 gcc 5.1.1 和 glibc 2.21。我希望我已经提供了足够的实现细节供任何人解释。
  • 这不是隐式或其他方式的转换(也没有从char**char 的隐式转换)。实际上传递给mainchar** 值很可能被解释为char 值,或者它的一部分。或者,根据实现的不同,如果 charchar** 以不同的方式作为参数传递,则某些未初始化的内存位置可能会被解释为 char 值。
【解决方案2】:

来自C标准,关于main()的签名

实现没有声明这个函数的原型。

因此,如果您传递不同类型的参数,编译器不会有任何问题。

在您的代码中,

int main(int agrc, char argv)

不是main() 推荐的签名。它应该是

int main(int agrc, char* argv[])

或者,至少

int main(int agrc, char** argv)

否则,在托管环境中,行为未定义。您可以在C11 标准的第 5.1.2.2.1 章中查看更多信息。

在您的情况下,如您所见,您将第二个参数设为 char 类型。根据标准规范,

如果argc 的值大于零,则数组成员argv[0]argv[argc-1] 应包含指向字符串的指针......

因此,在这里,提供的 0 被传递给 main() 作为指向 string 的指针,该指针在 char 中被接受,这不是定义的行为。

【讨论】:

  • 是的,他知道。他希望我们从实施的角度解释他得到的输出。你没有回答问题。 -1
  • 他没有问为什么它会给出奇怪的输出。他在问为什么它给出 this 输出。 “既然编译器没有给我报错,那么这些数字是从哪里来的?”
  • 轨道上的轻量级竞赛是正确的......我出于好奇而这样做了,我注意到了这种模式。我认为这种行为有一个特殊的原因......我他妈的,对吧?
  • @SouravGhosh:不,你仍然没有得到它。 “这不是一个明确的行为” 是你的答案。好吧,该死的,他知道。但是实现和运行时仍然在做一些导致这些值的事情,并且无论该行为是否被任何语言标准明确定义,这个 OP 都想知道那个“东西”是什么。
  • 另外,标记CC++,写Why does omitting * and [] ...这让我怀疑Yeah, he knows. He wanted us to explain the output he got from an implementation perspective. 这是一个相当随机的实验,看起来不错(或丑陋) *[]s.
【解决方案3】:

堆栈上有一个字符串指针,但您在那里声明了一个带有字符的main,然后将其打印为十进制。该字符串的内存地址不可预测,因此您会得到不可预测的输出。

试试这个:

int main( int argc, char* argv[] )
{
    printf( "%s", argv[1] );
    return 0;
}

我认为这会给你想要的。

【讨论】:

  • 这根本不能回答问题。发帖人想知道他们为什么得到他们所做的结果,而不是如何正确定义main
  • 根据他的声誉,我推断他真的希望它能够正常工作,所以我提供了。但是,我还编辑了答案以解决他的字面问题。
  • 对不起,不,你没有。
  • The memory address of that string is not predictable 的哪一部分你不明白?它说明了值的来源,并且没有确定的理由增加 8。
猜你喜欢
  • 2011-08-14
  • 2020-02-06
  • 2012-01-08
  • 1970-01-01
  • 2021-02-05
  • 1970-01-01
  • 2018-10-29
  • 2022-07-14
  • 1970-01-01
相关资源
最近更新 更多