4

プリプロセッサとCコンパイラ (具体的には gnu gcc) と文字列リテラルのいくつかの複雑さを理解しようとしています。#defineプリプロセッサ ディレクティブを使用するよりも、メモリ内の 1 つの場所しか占有しない文字列リテラルにグローバル変数を割り当てる方が効率的ですか?

この例のように、文字列リテラルはメモリ内に配置され、数回アクセスされます。

#include <stdio.h>
#include <string.h>
char OUTPUT[20] = "Hello, world!!!";
int main (){
    printf("%s is %d characters long.\n", OUTPUT, strlen(OUTPUT));
    return 0;
}

vsプリプロセッサでそれを行う:

#include <stdio.h>
#include <string.h>
#define OUTPUT "Hello, world!!!"

int main (){
    printf("%s is %d characters long.\n", OUTPUT, (int) strlen(OUTPUT));
    return 0;
}

これは次のように変換されます。

#include <stdio.h>
#include <string.h>
#define OUTPUT "Hello, world!!!"

int main (){
    printf("%s is %d characters long.\n", "Hello, world!!!", (int) strlen("Hello, world!!!"));
    return 0;
}

私が本当に求めているのは、プリプロセッサを使用した最後の 2 つの例で、コンパイラは「Hello, world!!!」の 2 つの別個のインスタンスを持っているかどうかです。2 つの別々のメモリ ロケーションにあるのか、それともコンパイラが 1 つのメモリ ロケーションにできるほどスマートなのか?

それが 2 つの別々のメモリ ロケーションである場合、プログラム定数のマクロ展開ではなく、グローバル変数を使用する方がリソースに優しくありませんか?

4

1 に答える 1

4

コンパイラは、文字列のインスタンスを 1 つ格納できるほどスマートでなければなりません。これは、プログラムのアセンブリ出力をチェックすることで確認できます。

たとえば、GCC を使用すると、次のようになります。

最初の例が「global.c」と呼ばれているとします。

gcc -Wall -S global.c

.file   "global.c"
.globl  OUTPUT
.data
.align 16
.type   OUTPUT, @object
.size   OUTPUT, 20
OUTPUT:
.string "Hello, world!!!"
.zero   4
.section    .rodata
.LC0:
.string "%s is %d characters long.\n"
.text
.globl  main
.type   main, @function
main: 
// More code...

プリプロセッサの例が「preproc.c」と呼ばれているとします。

gcc -Wall -S preproc.c
.file   "preproc.c"
.section    .rodata
.LC0:
.string "%s is %d characters long.\n"
.LC1:
.string "Hello, world!!!"
.text
.globl  main
.type   main, @function
main:
// More code...

どちらの場合も、「Hello, world!!!」のコピーは 1 つだけです。および「%s は %d 文字の長さです。\n」が存在します。最初の例では、コードに変更可能な配列があるため、20 文字分のスペースを節約する必要があります。これを変更した場合

char OUTPUT[20] = "Hello, world!!!";

const char * const OUTPUT = "Hello, world!!!";

あなたは得るでしょう:

.file   "global.c"
.globl  OUTPUT
.section    .rodata
.LC0:
.string "Hello, world!!!"
.align 8
.type   OUTPUT, @object
.size   OUTPUT, 8
OUTPUT:
.quad   .LC0
.LC1:
.string "%s is %d characters long.\n"
.text
.globl  main
.type   main, @function
main:
// More code...

これで、ポインターと文字列のためだけにスペースを節約できます。

この状況ではどちらが良いかは無視できますが、文字列のスコープがメイン関数内にとどまるようにプリプロセッサを使用することをお勧めします。

どちらも最適化されたほぼ同じコードを出力します。

( ) を含む Global.c const char * const OUTPUT:

gcc -Wall -O3 -S global.c

.file   "global.c"
.section    .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "Hello, world!!!"
.LC1:
.string "%s is %d characters long.\n"
.section    .text.startup,"ax",@progbits
.p2align 4,,15
.globl  main
.type   main, @function
main:
.LFB44:
.cfi_startproc
subq    $8, %rsp
.cfi_def_cfa_offset 16
movl    $15, %ecx
movl    $.LC0, %edx
movl    $.LC1, %esi
movl    $1, %edi
xorl    %eax, %eax
call    __printf_chk
xorl    %eax, %eax
addq    $8, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE44:
.size   main, .-main
.globl  OUTPUT
.section    .rodata
.align 8
.type   OUTPUT, @object
.size   OUTPUT, 8
OUTPUT:
.quad   .LC0
.ident  "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
.section    .note.GNU-stack,"",@progbits

Preproc.c

gcc -Wall -O3 -S preproc.c

    .file   "preproc.c"
.section    .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "Hello, world!!!"
.LC1:
.string "%s is %d characters long.\n"
.section    .text.startup,"ax",@progbits
.p2align 4,,15
.globl  main
.type   main, @function
main:
.LFB44:
.cfi_startproc
subq    $8, %rsp
.cfi_def_cfa_offset 16
movl    $15, %ecx
movl    $.LC0, %edx
movl    $.LC1, %esi
movl    $1, %edi
xorl    %eax, %eax
call    __printf_chk
xorl    %eax, %eax
addq    $8, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE44:
.size   main, .-main
.ident  "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
.section    .note.GNU-stack,"",@progbits

両方のmain関数を見ると、命令が同じであることがわかります。

于 2013-05-19T04:22:52.043 に答える