采访Hello World解释

时间:2010-10-01 06:23:39

标签: c obfuscation

这个classic ioccc entry是一个用C语言编写的Hello World程序。任何人都可以解释它是如何工作的吗?

原始代码(故意突出显示语法):

int i;main(){for(;i["]<i;++i){--i;}"];read('-'-'-',i+++"hell\
o, world!\n",'/'/'/'));}read(j,i,p){write(j/p+p,i---j,i/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);
}

5 个答案:

答案 0 :(得分:56)

for loop condition

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

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

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

因此,当位置i处的字符为\0时,循环将终止,即字符串末尾的字符长度为14个字符(其长度与“hello”相同) ,世界!\ n“)。因此for循环条件可以重写为:

i != 14

字符算术

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

char是整数类型,因此:

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

    读(0,i ++ +“你好,世界!\ 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函数冲突。)

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

read2

调用#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)中的1个字符写入文件描述符1(stdout)。 postdecrement是多余的,因为这个i是一个永远不会再引用的局部变量。所以这个函数相当于i

在主循环中内联putchar(*i)函数给出了

read2

意思明显。

答案 1 :(得分:17)

不要完全把它拆开,但有一些提示:

  • '-' - '-'0
  • 进行了模糊处理
  • '/' / '/'和read()中的i / i1
  • 进行了模糊处理

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

有了这个,找出其余部分应该比较容易。

编辑:upvotes让我有兴趣再看一下。

当然,i++ + "Hello, world!\n""Hello, world!\n"[ i++ ]相同。

Codelark已经指出0是stdout的fid。 (给他 +1,不是我。只是提到它的完整性。)

i中的read()当然是本地的,即i--中的read()不会影响i中的main()。由于以下i / i始终为1,因此--运算符根本不执行任何操作。

哦,告诉采访者解雇编写此代码的人。 它没有对write()的返回值进行错误检查。我可以使用声名狼借的代码(并且已经存在多年),但是没有检查错误比坏,那是错误的。 : - )

其余的只是一些三年级的数学。我把它传递给实习生。 ; - )

答案 2 :(得分:10)

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

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

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

第一次迭代=“h” 第二次迭代=“e”

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

答案 3 :(得分: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);
}

答案 4 :(得分:1)

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

因此读取功能变为

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

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

理解和解析这种代码实际上是毫无价值的。