前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >IOCCC 2020的一个项目

IOCCC 2020的一个项目

作者头像
云深无际
发布2021-04-14 14:24:38
6860
发布2021-04-14 14:24:38
举报
文章被收录于专栏:云深之无迹云深之无迹

IOCCC 大赛全称:The International Obfuscated C Code Contest,国际 C 语言混乱代码大赛,目的是写出最有创意的最让人难以理解的 C 语言代码,并限制在 4 kb 以内。这项赛事也年头了,2020 年是第 27 次举办。

代码语言:javascript
复制

#include <stdio.h>

#define N(a)       "%"#a"$hhn"
#define O(a,b)     "%10$"#a"d"N(b)
#define U          "%10$.*37$d"
#define G(a)       "%"#a"$s"
#define H(a,b)     G(a)G(b)
#define T(a)       a a 
#define s(a)       T(a)T(a)
#define A(a)       s(a)T(a)a
#define n(a)       A(a)a
#define D(a)       n(a)A(a)
#define C(a)       D(a)a
#define R          C(C(N(12)G(12)))
#define o(a,b,c)   C(H(a,a))D(G(a))C(H(b,b)G(b))n(G(b))O(32,c)R
#define SS         O(78,55)R "\n\033[2J\n%26$s";
#define E(a,b,c,d) H(a,b)G(c)O(253,11)R G(11)O(255,11)R H(11,d)N(d)O(253,35)R
#define S(a,b)     O(254,11)H(a,b)N(68)R G(68)O(255,68)N(12)H(12,68)G(67)N(67)

char* fmt = O(10,39)N(40)N(41)N(42)N(43)N(66)N(69)N(24)O(22,65)O(5,70)O(8,44)N(
            45)N(46)N    (47)N(48)N(    49)N( 50)N(     51)N(52)N(53    )O( 28,
            54)O(5,        55) O(2,    56)O(3,57)O(      4,58 )O(13,    73)O(4,
            71 )N(   72)O   (20,59    )N(60)N(61)N(       62)N (63)N    (64)R R
            E(1,2,   3,13   )E(4,    5,6,13)E(7,8,9        ,13)E(1,4    ,7,13)E
            (2,5,8,        13)E(    3,6,9,13)E(1,5,         9,13)E(3    ,5,7,13
            )E(14,15,    16,23)    E(17,18,19,23)E(          20, 21,    22,23)E
            (14,17,20,23)E(15,    18,21,23)E(16,19,    22     ,23)E(    14, 18,
            22,23)E(16,18,20,    23)R U O(255 ,38)R    G (     38)O(    255,36)
            R H(13,23)O(255,    11)R H(11,36) O(254    ,36)     R G(    36 ) O(
            255,36)R S(1,14    )S(2,15)S(3, 16)S(4,    17 )S     (5,    18)S(6,
            19)S(7,20)S(8,    21)S(9    ,22)H(13,23    )H(36,     67    )N(11)R
            G(11)""O(255,    25 )R        s(C(G(11)    ))n (G(          11) )G(
            11)N(54)R C(    "aa")   s(A(   G(25)))T    (G(25))N         (69)R o
            (14,1,26)o(    15, 2,   27)o   (16,3,28    )o( 17,4,        29)o(18
            ,5,30)o(19    ,6,31)o(        20,7,32)o    (21,8,33)o       (22 ,9,
            34)n(C(U)    )N( 68)R H(    36,13)G(23)    N(11)R C(D(      G(11)))
            D(G(11))G(68)N(68)R G(68)O(49,35)R H(13,23)G(67)N(11)R C(H(11,11)G(
            11))A(G(11))C(H(36,36)G(36))s(G(36))O(32,58)R C(D(G(36)))A(G(36))SS

#define arg d+6,d+8,d+10,d+12,d+14,d+16,d+18,d+20,d+22,0,d+46,d+52,d+48,d+24,d\
            +26,d+28,d+30,d+32,d+34,d+36,d+38,d+40,d+50,(scanf(d+126,d+4),d+(6\
            -2)+18*(1-d[2]%2)+d[4]*2),d,d+66,d+68,d+70, d+78,d+80,d+82,d+90,d+\
            92,d+94,d+97,d+54,d[2],d+2,d+71,d+77,d+83,d+89,d+95,d+72,d+73,d+74\
            ,d+75,d+76,d+84,d+85,d+86,d+87,d+88,d+100,d+101,d+96,d+102,d+99,d+\
            67,d+69,d+79,d+81,d+91,d+93,d+98,d+103,d+58,d+60,d+98,d+126,d+127,\
            d+128,d+129

char d[538] = {1,0,10,0,10};

int main() {
    while(*d) printf(fmt, arg);
}

以上是小哥哥写的code

代码为71个字节。不过我用GCC并没有编译通过。。。

不知道什么情况

玩法

代码语言:javascript
复制
gcc -o prog prog.c
./prog

在P1和P2之间交替。输入数字[1-9]移动:

代码语言:javascript
复制
1 | 2 | 3
---------
4 | 5 | 6
---------
7 | 8 | 9

如果游戏结束,则:

  • 玩家连续完成三场;那个玩家获胜
  • 所有正方形都被取走;双方都不赢
  • 玩家做出非法举动;对手获胜

混淆

不知道该怎么翻译

该程序的整体包括对printf的单个调用。

代码语言:javascript
复制
int main() {
    while(*d) printf(fmt, arg);
}

在这里,fmt是一个字符串,并且arg是printf的一系列参数。

尽管它的主要目的是充当The One True Debugger,但printf也恰好是Turing的完整版。

我们ab ^ H ^使用此事实,完全在此一个printf调用(以及对scanf()的调用以读取用户输入)内完全实现井字逻辑。

这里是(简要地)它是如何工作的。

一开始

该程序使用三个printf格式说明符。

  • %d 接受一个整数参数并打印
  • %s 接受字符串参数并打印
  • %n 接受一个指针并写入(!!)到目前为止已打印的字节数。

好吧,每个人都可能知道这一点。让我们更高级一些。

格式说明符可以包含额外的“参数”。

  • "%hhn":存储将mod 256写入char指针的字节数
  • "%2$d":将参数2打印到printf(而不是顺序的下一个参数)
  • "%8d":将打印的整数填充为8个字符
  • "%3

例如,以下表达式

代码语言:javascript
复制
printf("%1$.*2$d%3$hhn", 5, 10, &x)

与我们写过的一样

代码语言:javascript
复制
x = 10;

因为它将打印出来0000000005(将5填充为大小10),然后将写入x的字节数写入。

面向Printf的编程

好了,现在我们可以享受真正的乐趣了。

我们使用printf进行任意计算,将内存视为二进制数组-每对字节一位:

  • 零位由序列表示 00 00
  • 一位由序列表示,xx 00其中xx任何非零字节。

我们可以使用格式字符串来计算任意“位”的OR / NOT。

我们将从最简单的OR开始:

代码语言:javascript
复制
printf("%1$s%2$s%3$hhn", a, b, c)

将计算

代码语言:javascript
复制
*c = strlen(a) + strlen(b)

但假设strlen(x)对于1位是1,对于0位是0,我们有

代码语言:javascript
复制
*c = a | b

计算单个值的NOT也很容易:

代码语言:javascript
复制
printf("%1$255d%1$s%hhn", a, b)

将计算

代码语言:javascript
复制
*b = (strlen(a)+255)%256 = strlen(a)-1

再一次,因为strlen(x)1或者0我们有

代码语言:javascript
复制
*c = !b

从这里我们可以计算任何二进制电路。但是,高效地做事仍然需要工作。

井字游戏

游戏本身被表示为一个18位的棋盘,每个玩家9位,以及在玩家1和玩家2之间交替的转盘计数器。

为了检测谁赢了,我们执行以下逻辑。令A,B和C指向连续测试三个正方形的指针,而D则是是否有获胜的保存位置。

代码语言:javascript
复制
"%A$s%B$s%C$s%1$253d%11$hhn" // r11 = !(*A & *B & *C)
ZERO
"%11$s%1$255d%11hhn" // r11 = !r11
ZERO
"%11$s%D$s%D$hhn" // *D = *D | r11

也就是说,我们设置*D1如果有三个在一个排。对于两个播放器,我们针对所有可能的三合一配置重复此操作。

ZERO宏可确保通过以下表达式写出的字节数为0 mod 256

代码语言:javascript
复制
"%1$hhn%1$s" (repeated 256 times)

其中参数1是指向临时变量的指针,后跟一个空字节。

之所以起作用,是因为如果当前计数为0 mod 256,则“%1 hhn”将向参数1写入零,然后“%1 s”将永远不会发出任何文本。另一方面,如果计数不是0 mod 256,则将将长度为1的字符串写入参数1,然后“%1

类似地,检查是否存在无效移动。

为了确定要打印的内容,我们必须将“内存中”的位数组强制转换为Xs和Os进行打印。这实际上很简单。给定1 指向玩家1的平方的指针,2 指向玩家2的平方的指针,以及3

代码语言:javascript
复制
"%1$s" (repeated 47 times) "%2$s" (repeated 56 times) %1$32d%3$hhn"

实际上,它将计算

代码语言:javascript
复制
*r3 = (*r1) * 47 + (*r2) * 56 + 32

如果都不为真,则输出为'',如果r1为真,则输出为'X',如果r2为真,则输出'O'。

进一步的混淆

为了能够最终显示面板,同时仍然仅使用一个printf语句,我们使用

代码语言:javascript
复制
"\n\033[2J\n%26$s"

这是清除屏幕的转义序列,然后输出参数26。参数26是指向内存中char *的指针,该指针最初未定义,但是在printf语句中,我们将构造此字符串,使其看起来像tic-tac -脚趾板。

在木板之后,我们需要打印以下字符串之一:

代码语言:javascript
复制
P1>_
P2>_
P1 WINS
P2 WINS
P1 TIES
P2 TIES

根据轮到P1或P2进行移动,游戏结束并且有人赢了,或者游戏结束了,这是平局。

事实证明这并不像看起来那样难。使用与之前相同的技巧,将byte设置为

代码语言:javascript
复制
*byte4 = is_win * 'W' + is_tie * 'T'

字节'I''S'可以始终相同,我们对'E'/ 进行相同操作'N'

我们可以即时创建scanf()格式字符串,但是出于不同的原因。我们首先要运行printf()以显示第一个板,然后在运行scanf()printf()读取之间交替,然后显示移动。重要的是,我们希望有一个最终的scanf比赛结束时。它应该退出。

一种选择是将逻辑实现为

代码语言:javascript
复制
printf()
while (*ok) {
    scanf();
    printf();
}

但这将使我们需要的对printf的调用次数增加一倍。所以我们改为这样实现

代码语言:javascript
复制
while (*ok) {
    scanf();
    printf();
}

(实际上,我们实际上将它scanf()作为参数来避免使用多余的语句,但是它具有相同的效果。)

注意,现在没有initial printf()。为了确保程序不会在first之前阻塞printf(),但我们将scanf()格式初始化为null字符串,以便它立即返回而不会阻塞。第一次printf() 运行调用时,它将写出"%hhd"以创建create scanf()格式字符串。

代码语言:javascript
复制
https://github.com/carlini/printf-tac-toe
源地址,自己去玩
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-06-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 云深之无迹 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 混淆
    • 面向Printf的编程
      • 井字游戏
        • 进一步的混淆
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档