작은 Brainfuck 인터프리터가 있습니다. 그리고 그것을 컴파일하는 동안, gcc의 최적화 스위치를 변형하는 출력 크기가 예상 한 것과 다르다는 것을 알았습니다. 다음은 내가 컴파일 된 프로그램입니다 :gcc 최적화 스위치를 변형하면서 예기치 않은 파일 크기가 발생했습니다.
struct node {
struct node *prev;
int val;
struct node *jump;
struct node *next;
};
typedef struct node node;
node *newnode();
node *append(node *n);
node *prepend(node *n);
void erase(node *n);
int pop(node *n);
void doop(node *n);
node *link(node *n);
#include <stdlib.h>
// allocates a new node and sets all the things to zero
node *newnode() {
node *n = malloc(sizeof(node));
n->prev = n->next = n->jump = NULL;
n->val = 0;
return n;
}
// appends a node to a given node. assumes it is an end node
node *append(node *n) {
n->next = newnode();
n->next->prev = n;
return n->next;
}
// prepend node to list. assumes it is the first node
node *prepend(node *n) {
n->prev = newnode();
n->prev->next = n;
return n->prev;
}
// navigates to first node, then frees all the nodes, iterating to the end
void erase(node *n) {
node *m;
while (n->prev)
n = n->prev;
while (n) {
m = n->next;
free(n);
n = m;
}
}
// pops any node and links any connected nodes to each other
// returns value of erased node
int pop(node *n) {
int ret;
if (n->prev)
n->prev->next = n->next;
if (n->next)
n->next->prev = n->prev;
ret = n->val;
free(n);
return ret;
}
#include <stdio.h>
// bf tokens. all other are ignored
#define LSEEK '<'
#define RSEEK '>'
#define INCREMENT '+'
#define DECREMENT '-'
#define STDOUT '.'
#define STDIN ','
#define LBRACKET '['
#define RBRACKET ']'
// memory used by bf program. is this really turing-compliant?
char mem[30000] = { 0 };
// pointer used by bf program
char *ptr = mem;
// do operation beginning with given node
void doop(node *n) {
// copy node pointer in case we need the head of the list later
node *m = n;
// loop while node pointer is a valid one; e.g. stop at EOF
while (m) {
switch (m->val) {
// most of these are pretty self-explanatory
case LSEEK:
ptr--;
break;
case RSEEK:
ptr++;
break;
case INCREMENT:
(*ptr)++;
break;
case DECREMENT:
(*ptr)--;
break;
case STDOUT:
printf("%c", *ptr);
fflush(stdout);
break;
case STDIN:
*ptr = getchar();
break;
case LBRACKET:
// jump to closing bracket if value at pointer is false
if (!*ptr)
m = m->jump;
break;
case RBRACKET:
// jump back to opening bracket if value at pointer is true
if (*ptr)
m = m->jump;
break;
}
// proceed to next instruction
m = m->next;
}
}
// finds and references each bracket instruction to its corresponding bracket
node *link(node *n) {
// make a copy of the list head
node *m = n;
// make a temporary list to contain bracket links
node *links = newnode();
// while a valid node
while (m) {
// switch to bracket type
switch (m->val) {
case LBRACKET:
// this is an opening bracket, so we temporarily store it's
// location, and append the list as it grows
if (links->jump)
links = append(links);
links->jump = m;
break;
case RBRACKET:
// this is the closing bracket, so we save the temporarily
// stored link address to the closing bracket node, and
// connect the opening bracket node to the closing also;
// popping the list as we no longer need the data
m->jump = links->jump;
links->jump->jump = m;
if (links->prev) {
links = links->prev;
pop(links->next);
}
break;
}
// increment to next character
m = m->next;
}
// erase all the nodes in the temporary linked list
erase(links);
// return the head of the list
return n;
}
#include <signal.h>
// press ctrl-c then enter to quit if not running from a file
int run = 1;
void quit(int val) {
run = 0;
}
int main(int argc, char** argv) {
// catch crtl-c
signal(SIGINT, quit);
int c;
// our text structure is a linked list
node *text, *text_start;
if (argc > 1) {
// print the file name
printf("%s\n", argv[1]);
// open the file and read it to the linked list
FILE *f = fopen(argv[1], "r");
if (f == NULL) return 1;
text = text_start = newnode();
while ((c = fgetc(f)) != EOF) {
if (text->val)
text = append(text);
text->val = c;
}
fclose(f);
// link all the loops/ gotos, then process all instructions
doop(link(text_start));
// free all linked list nodes
erase(text_start);
// we just ran a file, so no interpreter
run = 0;
}
// repeatedly read and execute only one line until interrupted
while (run) {
// linked list generated for each line of input
text = text_start = newnode();
// read stdin buffer to list
while ((c = getchar()) != '\n') {
if (text->val)
text = append(text);
text->val = c;
}
// link all the loops/ gotos, then process the
// instructions for the line
doop(link(text_start));
// free all linked list nodes
erase(text_start);
}
return 0;
}
마지막으로, 다음은 예기치 않은 파일 크기에서 파생 컴파일 순서입니다 :
$ gcc brainfuck.c -o brainfuck -Os && ls brainfuck -l && for i in `seq 0 3`; do gcc brainfuck.c -o brainfuck -O$i && ls brainfuck -l; done && gcc --version
-rwxrwxr-x 1 m m 13552 Oct 4 18:31 brainfuck
-rwxrwxr-x 1 m m 13544 Oct 4 18:31 brainfuck
-rwxrwxr-x 1 m m 13600 Oct 4 18:31 brainfuck
-rwxrwxr-x 1 m m 13600 Oct 4 18:31 brainfuck
-rwxrwxr-x 1 m m 13600 Oct 4 18:31 brainfuck
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.2) 5.4.0 20160609
무엇을 기대 했습니까? –
@ M.M 나는 파일 크기가 훨씬 더 다양 할 것으로 예상했다. – motoku
코드에 최적화가되지 않을 수도 있습니다. 서로 다른 버전간에 생성 된 어셈블리를 비교하여 정확히 무엇이 변경되었는지 확인할 수 있습니다. –