2016-10-05 13 views
1

작은 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 
+1

무엇을 기대 했습니까? –

+0

@ M.M 나는 파일 크기가 훨씬 더 다양 할 것으로 예상했다. – motoku

+2

코드에 최적화가되지 않을 수도 있습니다. 서로 다른 버전간에 생성 된 어셈블리를 비교하여 정확히 무엇이 변경되었는지 확인할 수 있습니다. –

답변

4

작은 바이너리의 크기의 큰 거래는 상용구가 될 것입니다 시작, 디버깅 기호 테이블 플러스 전역 데이터 영역 및 기타 섹션에서 많은 제로 패딩. null 채우기에 대한 2 진 검사를 수행하십시오. 다소 현실적인 비율이이되도록하려면 기호를 제거하십시오.

정말 TEXT 섹션의 크기를 비교해야합니다. 즉, 전체 Unix 실행 형식 바이너리가 아닌 명령 스트림입니다.

코드를 최적화하면 이 매우 넓어서 크기에 예측할 수없는 영향을 미칩니다. 언 롤링 (unrolling) 루프는 인라인뿐만 아니라 코드를 길게하지만 중복 메모리로드/저장, 일반적인 하위 표현식 제거, 불필요한 코드 제거 및 상수 폴딩을 제거하면 크기가 줄어 듭니다. 그래서 여러분은이 반대 세력을 요약 할 때 극히 불투명 한 시각을 가지고 있습니다. 정말로 뭔가 배우고 싶다면 조립품을 한 줄씩 나란히 조사하십시오. gcc -S을보고 다시 신고하십시오.

또한 대부분의 에너지를 I/O 스트림과주고받는 에너지를 소비하는 경우 많은 코드가 최적화되지 않을 것이라는 의견이 맞습니다. 최적화는 CPU 바운드 및 메모리 바운드 자료에서 작동합니다.

% gcc -OS -o bfos brainfuck.C# -OS is optimize but keep code small 
% objdump -h bfos | grep text 
12 .text   00000452 0000000000400730 0000000000400730 00000730 2**4 

% gcc -O0 -o bfo0 brainfuck.C# -O0 is default: no optimizations 
% objdump -h bfo0 | grep text 
12 .text   00000652 0000000000400730 0000000000400730 00000730 2**4 

0x452/0x652 = 큰 차이가 있습니다. 아직

그리고 바이너리 크기가 몇 배는있는 패딩을 가지고 있고, 컴파일 된 코드의 크기와는 아무 상관이 없다 : 마지막으로

% ls -l bfo0 bfos 
-rwxr-xr-x 1 root root 13461 Oct 4 22:42 bfo0 
-rwxr-xr-x 1 root root 13469 Oct 4 22:41 bfos 

% gcc --version 
gcc (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4 

을, 제로 패딩의 길이 뻗어합니다 (이 '*'모든 반복을 의미합니다 0x000760에서 0x0006700까지 모두 0 바이트입니다.

% od -x bfo0 | grep -1 '\*' 
0000720 0000 0000 0000 0000 0000 0000 0000 0000 
* 
0000760 0000 0000 0000 0000 0010 0000 0000 0000 
-- 
0006700 0a8c 0040 0000 0000 0a8c 0040 0000 0000 
* 
0007040 09c9 0040 0000 0000 0a8c 0040 0000 0000 
-- 
0007100 0a8c 0040 0000 0000 0a8c 0040 0000 0000 
* 
0007420 0a8c 0040 0000 0000 0a51 0040 0000 0000 
-- 
0010640 0000 0000 0000 0000 0000 0000 0000 0000 
* 
0017020 07f0 0040 0000 0000 07d0 0040 0000 0000 
-- 
0017640 0000 0000 0000 0000 0000 0000 0000 0000 
* 
0020000 1e28 0060 0000 0000 0000 0000 0000 0000 
-- 
0020720 0000 0000 0000 0000 0000 0000 0000 0000 
* 
0021000 0000 0000 0000 0000 001b 0000 0001 0000 
-- 
0024500 0000 0000 0000 0000 0000 0000 0000 0000 
* 
0024540 0000 0000 0003 0001 0238 0040 0000 0000