这个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);
}
答案 0 :(得分:56)
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 / i
对1
请记住[]是可交换的,即i["]<i;++i){--i;}"]
与"]<i;++i){--i;}"[i]
相同(具有char数组并指向它的第i个元素)。字符串的 content 以任何方式无关紧要,只有 length 用于定义循环的迭代次数。任何其他长度相同的字符串(在内部,与输出相同的长度......)都可以工作。在那个迭代次数之后,“string”[i]返回终止字符串的空字符。零== false,循环终止。
有了这个,找出其余部分应该比较容易。
编辑:upvotes让我有兴趣再看一下。
当然,i++ + "Hello, world!\n"
与"Hello, world!\n"[ i++ ]
相同。
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循环中[]运算符的可交换性。
理解和解析这种代码实际上是毫无价值的。