在改变gcc优化开关时出现意外的文件大小

时间:2016-10-05 01:36:06

标签: c gcc compiler-optimization compiler-options

我有一个小脑袋翻译,我写了一会儿;在编译时,我注意到为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 :(得分:4)

大量小二进制文件的大小将是样板启动,加上调试符号表,加上全局数据区域和其他部分的大量零填充。对空填充执行二进制检查。要获得稍微更真实的比例,请删除符号。

你应该只是比较TEXT部分的大小,即指令流而不是整个Unix可执行格式二进制文件。

优化代码也会对大小产生非常不可预测的影响。展开循环会延长代码和内联,但删除冗余内存加载/存储,公共子表达式消除,死代码消除和常量折叠会减小大小。因此,在总结这些对立的力量时,你会有一个非常不透明的视角。如果你真的想学习一些东西,那就一行一行地研究装配。请参阅gcc -S并报告回来。

另外,评论是正确的,如果您将大部分能量传输到I / O流和从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

最后,长时间的零填充(&#39; *&#39;表示所有重复,因此从0x000760到0x0006700,它都是零字节)

% 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