采访Hello World解释

Jos*_*shD 27 c obfuscation

这个经典的ioccc条目是一个用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);
}
Run Code Online (Sandbox Code Playgroud)

dan*_*n04 56

用于循环条件

i["]<i;++i){--i;}"]
Run Code Online (Sandbox Code Playgroud)

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

"]<i;++i){--i;}"[i]
Run Code Online (Sandbox Code Playgroud)

因此,当位置i处的字符(\0即字符串的末尾)长度为14个字符(这恰好与"hello,world!\n"相同)时,循环将终止.因此for循环条件可以重写为:

i != 14
Run Code Online (Sandbox Code Playgroud)

字符算术

read('-' - '-', i++ + "hello, world!\n", '/' / '/')
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

(我重命名readread2避免与Unix read函数冲突.)

请注意,不需要jp参数read2,因为始终使用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);
}
Run Code Online (Sandbox Code Playgroud)

该调用write(1, i--, 1)将1个字符写入i文件描述符1(stdout).并且postdecrement是多余的,因为这i是一个从未再次引用的局部变量.所以这个功能相当于putchar(*i).

内联read2主循环中的函数给出了

#include <stdio.h>

int i = 0;

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

   return 0;
}
Run Code Online (Sandbox Code Playgroud)

其含义是显而易见的.


Dev*_*lar 17

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

  • '-' - '-' 被混淆了 0
  • '/' / '/'i / iread()被混淆了1

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

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

编辑: upvotes让我有兴趣再看一下这个.

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

codelark已经指出这0是stdout的fid.(给 + 1,不是我.只是提到它完整.)

iread()是当然的地方,即i--read()不影响imain().由于以下i / i总是1无论如何,--操作员根本不做任何事情.

哦,并告诉采访者解雇编写此代码的人.它不会对返回值进行错误检查write().我可以忍受着名的代码写得很糟糕(多年来一直有代码),但是没有检查错误比糟糕更糟糕,这是错误的.:-)

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


cod*_*ark 10

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

first iteration ="hello .."second iteration ="ello .."

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

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

在for循环的字符串索引是相同长度的hello world字符串,并为[]是可交换的,并且串空终止,最后一次迭代将返回一个空字符,退出循环.