【问题标题】:Interview Hello World explanation采访Hello World解释
【发布时间】:2011-04-19 17:08:44
【问题描述】:

这个classic ioccc entry 是一个用C 编写的Hello World 程序。谁能解释一下它是如何工作的?

原始代码(有意遗漏语法高亮):

int i;main(){for(;i["]

稍微干净一点:

int i;
main()
{
  for ( ; i["]<i;++i){--i;}"]; read('-' - '-', i++ + "hello, world!\n", '/' / '/'));
}

read(j, i, p)
{
  write(j / p + p, i-- - j, i / i);
}

【问题讨论】:

  • 为什么会有人在面试时问关于混淆代码的问题?!​​
  • 从纯语言的角度来看,一点也不,因为write() 没有定义。 ;-)(我知道这可能意味着调用 POSIX write() 函数,但没有在任何地方说明。)
  • 更不用说read()的K&R风格声明了。它可能无法使用现代编译器按原样编译。如果这出现在采访中,它只会作为人们围绕代码玩的游戏的对话开始才有意义,并了解受害者如何解决这个难题......
  • 是的,有一些假设。 @Brian:这可能是因为解释它是如何工作的需要大量的理解,一个平庸的程序员可能没有的知识......也许。或者面试官认为他真的很聪明。
  • @Fabian:语法上是一样的。 [] 运算符是可交换的。

标签: c obfuscation


【解决方案1】:

for循环条件

i["]<i;++i){--i;}"]

这个表达式利用了数组索引在 C 中是可交换的这一事实。它等价于。

"]<i;++i){--i;}"[i]

所以当i位置的字符为\0时,循环将终止,即在字符串的末尾,长度为14个字符(恰好与“hello, world!\ n")。所以for循环条件可以改写为:

i != 14

字符算术

read('-' - '-', i++ + "hello, world!\n", '/' / '/')

char 是整数类型,因此:

  • '-' - '-' 为 0
  • '/' / '/' 是 1

    read(0, i++ + "hello, world!\n", 1)


修复所有编译器警告(如隐式 int 到指针转换),并简化上面提到的事情后,代码变为:

#include <unistd.h>

int i = 0;

void read2(int, char*, int);

int main()
{
   while (i != 14)
   {
      read2(0, i++ + "hello, world!\n", 1);
   }

   return 0;
}

void read2(int j, char* i, int p)
{
   write(j / p + p, i-- - j, 1);
}

(我将 read 重命名为 read2 以避免与 Unix 的 read 函数冲突。)

请注意,read2jp 参数是不需要的,因为函数始终使用 j=0 和 p=1 调用。

#include <unistd.h>

int i = 0;

void read2(char*);

int main()
{
   while (i != 14)
   {
      read2(i++ + "hello, world!\n");
   }

   return 0;
}

void read2(char* i)
{
   write(1, i--, 1);
}

调用write(1, i--, 1)i 中的1 个字符写入文件描述符1 (stdout)。并且后减是多余的,因为这个i 是一个不再被引用的局部变量。所以这个函数相当于putchar(*i)

在主循环中内联 read2 函数给出

#include <stdio.h>

int i = 0;

int main()
{
   while (i != 14)
   {
      putchar(*(i++ + "hello, world!\n"));
   }

   return 0;
}

意思很明显。

【讨论】:

  • 第三个代码块中的错字: void read2(char* i) { write(1, i--, 1); } 应该是: void read2(char* i) { write(0, i--, 1);其中 0 是 STDOUT 的文件描述符。
  • @JonathanBranam 不,0 是标准输入的文件描述符。 1 是标准输出,2 是标准错误。
【解决方案2】:

不打算完全分解,但有一些提示:

  • '-' - '-' 被混淆为 0
  • '/' / '/' 和 read() 中的 i / i 被混淆为 1

记住 [] 是可交换的,即 i["]&lt;i;++i){--i;}"]"]&lt;i;++i){--i;}"[i] 相同(有一个 char 数组并指向它的第 i 个元素)。字符串的 content 无论如何都无关紧要,只有它的 length 用于定义循环的迭代次数。任何其他长度相同的字符串(顺便说一下,与输出长度相同......)都可以工作。在该迭代次数之后,“string”[i] 返回终止字符串的空字符。零 == false,循环终止。

有了这个,剩下的应该比较容易弄清楚了。

编辑:这些点赞让我有足够的兴趣来看看这个。

当然,i++ + "Hello, world!\n""Hello, world!\n"[ i++ ] 是一样的。

codelark 已经指出0 是标准输出的fid。 (为此, +1,而不是我。只是为了完整起见。)

read() 中的i 当然是本地的,即read() 中的i-- 不会影响main() 中的i。因为下面的i / i 总是1,所以-- 操作符什么都不做。

哦,告诉面试官解雇编写此代码的人。 它不会对 write() 的返回值进行错误检查。 我可以忍受臭名昭著的写得很糟糕的代码(并且多年来一直如此),但不检查错误比糟糕更糟糕,这是错误。 :-)

剩下的只是一些三年级的数学。我会把它传给实习生。 ;-)

【讨论】:

    【解决方案3】:

    i["..."] 的字符串索引导致循环执行字符串的长度。 i++ + hello world 字符串意味着每次迭代都会将字符串开头的一个字母更深地传递给本地读取函数。

    第一次迭代 = “你好..” 第二次迭代 = “你好..”

    read 函数的第一个参数简化为 0,即标准输出的文件描述符。最后一个参数简化为 1,因此每次调用只写入一个字符。这意味着每次迭代都将写入传递给 stdout 的字符串的第一个字符。所以按照上面的示例字符串:

    第一次迭代 = "h" 第二次迭代 = "e"

    for循环中的字符串索引与hello world字符串长度相同,并且由于[]是可交换的,并且字符串以null结尾,最后一次迭代将返回null字符并退出循环。

    【讨论】:

      【解决方案4】:

      远非 C 专家,但我会尝试:

      i["]<i;++i){--i;}"]
      // is actually
      char* x = "]<i;++i){--i;}"
      *(i + x)
      // and will turn to zero when i == 14 (will point to string's ending zero)
      // '-' - '-' and '/' / '/' are always 0 and 1
      // int i is initiated to zero
      // i++ will return original value, so main turns to
      main()
      {
          char* hello = "hello, world!\n";
          for (i = 0 ; i != 14; i++) read(0, hello[i], 1);
      }
      
      // read becomes
      // i-- does nothing here, i is in read's scope, not the global one
      read(j, i, p)
      {
        write(1, i, 1);
      }
      
      // and at last
      main()
      {
          char* hello = "hello, world!\n";
          for (i = 0 ; i<14; i++) write(1, hello[i], 1);
      }
      

      【讨论】:

        【解决方案5】:

        又一个面试问题似乎更多地反映了面试官而不是受访者。 这里的关键问题: * j 始终为 0 (('-' - '-') == 0) * p 始终为 1 (('/' / '/') == 1) * i(在读取函数中)是 (i++ + "hello world") == hello world 中的第 i 个字符(然后递增 i)

        这样读取函数就变成了

        read(0, NextChar, 1)
        {
          write(1, NextChar , 1);
        }
        

        唯一的另一位是 for 循环中 [] 运算符的可交换性。

        理解和解析这种代码真的没有价值。

        【讨论】:

        • [] 运算符的交换性是这段代码中唯一有价值的知识恕我直言...
        • @DevSolar - 是的 - 很公平;编辑我的答案以使我的意思更清楚
        • 当我第一次遇到这个时,那是一件真正让我感到惊讶的事情。不过,我有 C++ 背景。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-01-16
        • 2020-08-21
        • 2012-01-04
        • 2014-10-15
        • 2023-04-11
        • 1970-01-01
        • 2021-09-17
        相关资源
        最近更新 更多