정적 변수는 C와 C ++에 어디에 저장되어 있습니까?


180

실행 파일의 어떤 세그먼트 (.BSS, .DATA, 기타)에 이름 충돌이 없도록 정적 변수가 저장됩니까? 예를 들면 다음과 같습니다.


foo.c:                         bar.c:
static int foo = 1;            static int foo = 10;
void fooTest() {               void barTest() {
  static int bar = 2;            static int bar = 20;
  foo++;                         foo++;
  bar++;                         bar++;
  printf("%d,%d", foo, bar);     printf("%d, %d", foo, bar);
}                              }

두 파일을 모두 컴파일하고 fooTest () 및 barTest를 반복적으로 호출하는 주 파일에 연결하면 printf 문이 독립적으로 증가합니다. foo 및 bar 변수는 번역 단위에 로컬이기 때문에 의미가 있습니다.

그러나 스토리지는 어디에 할당됩니까?

분명히 말하면, ELF 형식으로 파일을 출력하는 툴체인이 있다고 가정합니다. 따라서, 나는 생각 이 것을 가지고 일부 공간이 그 정적 변수의 실행 파일에 예약 할 수 있습니다.
토론을 위해 GCC 툴체인을 사용한다고 가정하겠습니다.


1
대부분의 사람들은 질문에 대답하는 대신 .DATA 섹션에 저장해야한다고 말합니다. 정확히 .DATA 섹션의 위치와 위치를 찾을 수있는 방법. 이미 답변을 표시 한 것을 확인하는 방법을 알고 있습니까?
lukmac

: 초기화 및 다른 섹션에 배치됩니다 초기화되지 않은 이유를 linuxjournal.com/article/1059
MHK

1
런타임시 전역 / 정적 변수에 할당 된 스토리지는 이름 확인과 관련이 없으며 빌드 / 링크 시간 동안 발생합니다. 실행 파일이 빌드 된 후 더 이상 이름이 없습니다.
valdo

2
이 질문은 의미가 없으며, 내보내지지 않은 심볼의 "이름 충돌"이 존재할 수 있다는 잘못된 전제에 기초하고 있습니다. 합법적 인 질문이 없다는 사실은 일부 답변이 얼마나 무서운지를 설명 할 수 있습니다. 그렇게 많은 사람들이 이것을 믿기 어렵습니다.
underscore_d

답변:


131

정적 위치는 0으로 초기화 되었는지 여부에 따라 다릅니다 . 0으로 초기화 된 정적 데이터는 .BSS (Block Started by Symbol)에 있고 0으로 초기화되지 않은 데이터는 .DATA에 있습니다.


50
"0이 초기화되지 않음"은 아마도 "초기화되었지만 0 이외의 다른 것"을 의미합니다. C / C ++에는 "초기화되지 않은"정적 데이터와 같은 것이 없기 때문입니다. 모든 정적 항목은 기본적으로 0으로 초기화됩니다.
AnT

21
@ Don Neufeld : 당신의 대답은 질문에 전혀 대답하지 않습니다. 나는 그것이 왜 받아 들여 지는지 이해하지 못한다. 'foo'와 'bar'는 모두 0으로 초기화되지 않기 때문입니다. 문제는 같은 이름을 가진 두 개의 정적 / 전역 변수를 .bss 또는 .data에 배치 할 위치입니다.
lukmac

명시 적으로 0으로 초기화 된 .data정적 데이터가 들어가고 초기화 프로그램이없는 정적 데이터가 들어간 구현을 사용했습니다 .bss.
MM

1
@MM 제 경우에는 정적 멤버가 초기화되지 않았는지 (암시 적으로 0으로 초기화 됨) 명시 적으로 0으로 초기화되었는지 여부에 관계없이 두 경우 모두 .bss 섹션에 추가되었습니다.
cbinder

이 정보는 특정 실행 파일 유형에만 해당됩니까? 지정하지 않았기 때문에 적어도 ELF 및 Windows PE 실행 파일에 적용되지만 다른 유형은 어떻습니까?
Jerry Jeremiah

116

프로그램이 메모리에로드되면 다른 세그먼트로 구성됩니다. 세그먼트 중 하나는 DATA segment 입니다. 데이터 세그먼트는 두 부분으로 세분됩니다.

초기화 된 데이터 세그먼트 : 모든 글로벌, 정적 및 상수 데이터가 여기에 저장됩니다.
초기화되지 않은 데이터 세그먼트 (BSS) : 초기화되지 않은 모든 데이터가이 세그먼트에 저장됩니다.

이 개념을 설명하는 다이어그램은 다음과 같습니다.

여기에 이미지 설명을 입력하십시오


다음은 이러한 개념을 설명하는 매우 유용한 링크입니다.

http://www.inf.udec.cl/~leo/teoX.pdf


위의 대답은 0 초기화가 BSS에 들어간다고 말합니다. 0 초기화는 초기화되지 않았거나 0 자체를 의미합니까? 그 자체가 0을 의미한다면 답에 포함시켜야한다고 생각합니다.
Viraj

상수 데이터는 .data 세그먼트에 저장되지 않고 텍스트 섹션의 .const 세그먼트에 저장됩니다.
user10678

이 대신에 ( " 초기화 데이터 세그먼트 : 모든 전역, 정적 및 상수 데이터가 여기에 저장됩니다. 초기화되지 않은 데이터 세그먼트 (BSS) : 초기화되지 않은 모든 데이터가이 세그먼트에 저장됩니다."), 다음과 같이 말해야합니다. ( " 초기화 된 데이터 세그먼트 : 0이 아닌 값으로 초기화 된 모든 전역 및 정적 변수와 모든 상수 데이터가 여기에 저장됩니다 초기화되지 않은 데이터 세그먼트 (BSS) : 초기화되지 않았거나 초기화 된 모든 전역 및 정적 변수 이 세그먼트에 저장됩니다. ").
Gabriel Staples

또한 내가 이해하는 한 "초기화 데이터"는 초기화 된 변수 상수 로 구성 될 수 있습니다 . 마이크로 컨트롤러 (예 : STM32)에서 초기화 된 변수 는 기본적으로 플래시 메모리 에 저장되고 시작시 RAM에 복사되며 , 초기화 된 상수텍스트 와 함께 Flash 전용 으로 남겨지고 읽을 수 있습니다. 프로그램 자체이며 플래시에만 남아 있습니다.
가브리엘 스테이플 21

따라서이 다이어그램에서 수집하는 것은 전역 변수 또는 정적 변수 (정적 변수는 지속 기간의 전역 변수처럼 작동하므로)는 힙 이나 스택이 아니라 두 변수와 별도로 메모리에 할당된다는 것입니다. 맞습니까? 메모리 할당을 더 연구하기 위해 STM32 링커 스크립트를 다시 살펴볼 수 있다고 가정합니다.
Gabriel Staples

32

실제로 변수는 튜플 (저장소, 범위, 유형, 주소, 값)입니다.

storage     :   where is it stored, for example data, stack, heap...
scope       :   who can see us, for example global, local...
type        :   what is our type, for example int, int*...
address     :   where are we located
value       :   what is our value

로컬 범위는 정의 된 위치에 따라 변환 단위 (소스 파일), 함수 또는 블록 중 하나에 로컬을 의미 할 수 있습니다. 하나 이상의 함수가 변수를 볼 수있게하려면 분명히 명시 적으로 초기화되었는지 여부에 따라 DATA 또는 BSS 영역에 있어야합니다. 그런 다음 소스 파일 내의 모든 기능 또는 기능에 따라 범위가 지정됩니다.


21

데이터의 저장 위치는 구현에 따라 다릅니다.

그러나 정적 의 의미 는 "내부 연결"입니다. 따라서 심볼은 컴파일 단위 (foo.c, bar.c) 내부 에 있으며 해당 컴파일 단위 외부에서 참조 할 수 없습니다. 따라서 이름 충돌이 없습니다.


아니. 정적 키 월드는 오버로드 된 의미를가집니다. 이러한 경우 정적은 링크 수정자가 아닌 스토리지 수정 자입니다.
ugasoft

4
ugasoft : 함수 외부의 스태틱은 링키지 수정 자이며, 내부는 시작될 충돌이없는 스토리지 수정 자입니다.
wnoise

12

"전역 및 정적"영역에서 :)

C ++에는 몇 가지 메모리 영역이 있습니다.

  • 더미
  • 무료 상점
  • 스택
  • 글로벌 및 정적
  • const

질문에 대한 자세한 답변 은 여기 를 참조 하십시오 .

다음은 C ++ 프로그램의 주요 메모리 영역을 요약 한 것입니다. 일부 이름 (예 : "힙")은 [표준] 초안에 표시되지 않습니다.

     Memory Area     Characteristics and Object Lifetimes
     --------------  ------------------------------------------------

     Const Data      The const data area stores string literals and
                     other data whose values are known at compile
                     time.  No objects of class type can exist in
                     this area.  All data in this area is available
                     during the entire lifetime of the program.

                     Further, all of this data is read-only, and the
                     results of trying to modify it are undefined.
                     This is in part because even the underlying
                     storage format is subject to arbitrary
                     optimization by the implementation.  For
                     example, a particular compiler may store string
                     literals in overlapping objects if it wants to.


     Stack           The stack stores automatic variables. Typically
                     allocation is much faster than for dynamic
                     storage (heap or free store) because a memory
                     allocation involves only pointer increment
                     rather than more complex management.  Objects
                     are constructed immediately after memory is
                     allocated and destroyed immediately before
                     memory is deallocated, so there is no
                     opportunity for programmers to directly
                     manipulate allocated but uninitialized stack
                     space (barring willful tampering using explicit
                     dtors and placement new).


     Free Store      The free store is one of the two dynamic memory
                     areas, allocated/freed by new/delete.  Object
                     lifetime can be less than the time the storage
                     is allocated; that is, free store objects can
                     have memory allocated without being immediately
                     initialized, and can be destroyed without the
                     memory being immediately deallocated.  During
                     the period when the storage is allocated but
                     outside the object's lifetime, the storage may
                     be accessed and manipulated through a void* but
                     none of the proto-object's nonstatic members or
                     member functions may be accessed, have their
                     addresses taken, or be otherwise manipulated.


     Heap            The heap is the other dynamic memory area,
                     allocated/freed by malloc/free and their
                     variants.  Note that while the default global
                     new and delete might be implemented in terms of
                     malloc and free by a particular compiler, the
                     heap is not the same as free store and memory
                     allocated in one area cannot be safely
                     deallocated in the other. Memory allocated from
                     the heap can be used for objects of class type
                     by placement-new construction and explicit
                     destruction.  If so used, the notes about free
                     store object lifetime apply similarly here.


     Global/Static   Global or static variables and objects have
                     their storage allocated at program startup, but
                     may not be initialized until after the program
                     has begun executing.  For instance, a static
                     variable in a function is initialized only the
                     first time program execution passes through its
                     definition.  The order of initialization of
                     global variables across translation units is not
                     defined, and special care is needed to manage
                     dependencies between global objects (including
                     class statics).  As always, uninitialized proto-
                     objects' storage may be accessed and manipulated
                     through a void* but no nonstatic members or
                     member functions may be used or referenced
                     outside the object's actual lifetime.

12

나는 충돌이있을 것이라고 믿지 않는다. 파일 수준에서 외부 함수를 사용하면 (외부 함수) 변수를 현재 컴파일 단위 (파일)의 로컬로 표시합니다. 현재 파일 외부에서는 볼 수 없으므로 외부에서 사용할 수있는 이름이 없어도됩니다.

함수 에서 static을 사용 하는 것은 다릅니다. 변수는 함수에만 표시되며 (정적이든 아니든), 해당 함수에 대한 호출에서 값이 유지됩니다.

실제로 static은 위치에 따라 두 가지 다른 작업을 수행합니다. 그러나 경우 모두 연결시 네임 스페이스 충돌을 쉽게 방지 할 수있는 방식으로 변수 가시성이 제한됩니다.

나는 그것이 DATA섹션이 아닌 다른 값으로 초기화되는 변수를 갖는 경향이 있다고 생각합니다 . 물론 이것은 표준에 의해 지시 된 것이 아니라 구현 세부 사항입니다. 그것은 커버 아래에서 일이 수행되는 방식이 아니라 행동 에만 관심 이 있습니다.


1
@ paxdiablo : 두 가지 유형의 정적 변수를 언급했습니다. 이 기사 ( en.wikipedia.org/wiki/Data_segment ) 중 어느 것을 언급합니까? 데이터 세그먼트에는 전역 변수 (정확한 변수와 본질적으로 반대되는 변수)도 있습니다. So, how does a segment of memory (Data Segment) store variables that can be accessed from everywhere (global variables) and also those which have limited scope (file scope or function scope in case of static variables)?
Lazer

@eSKay, 그것은 가시성과 관련이 있습니다. 컴파일 유닛에 로컬 인 세그먼트에 저장된 것들이있을 수 있고, 다른 것들은 완전히 액세스 가능한 것들이 있습니다. 하나의 예 : 각 세그먼트가 DATA 세그먼트에 블록을 제공한다고 생각하십시오. 해당 블록의 모든 위치를 알고 있습니다. 또한 다른 comp-unit이 액세스하기를 원하는 블록에 해당 사물의 주소를 게시합니다. 링커는 링크 타임에 해당 주소를 확인할 수 있습니다.
paxdiablo

11

자신을 찾는 방법 objdump -Sr

실제로 무슨 일이 일어나고 있는지 이해하려면 링커 재배치를 이해해야합니다. 만지지 않았다면 먼저이 게시물을 읽어 보십시오 .

리눅스 x86-64 ELF 예제를 분석해 보자.

#include <stdio.h>

int f() {
    static int i = 1;
    i++;
    return i;
}

int main() {
    printf("%d\n", f());
    printf("%d\n", f());
    return 0;
}

다음과 같이 컴파일하십시오.

gcc -ggdb -c main.c

다음을 사용하여 코드를 디 컴파일하십시오.

objdump -Sr main.o
  • -S 원본 소스가 섞인 코드를 디 컴파일
  • -r 재배치 정보를 보여줍니다

디 컴파일 내부에서 f우리는 다음을 봅니다.

 static int i = 1;
 i++;
4:  8b 05 00 00 00 00       mov    0x0(%rip),%eax        # a <f+0xa>
        6: R_X86_64_PC32    .data-0x4

그리고 .data-0x4그것은 .data세그먼트 의 첫 번째 바이트로 갈 것이라고 말합니다 .

-0x4우리는 따라서, 어드레싱 RIP 상대적인 사용하고 있기 때문에 거기 %rip명령과 R_X86_64_PC32.

RIP가 다음 명령을 가리 키기 때문에 필요합니다. 다음 명령은 4 바이트 후에 시작 00 00 00 00하여 재배치됩니다. https://stackoverflow.com/a/30515926/895245 에서 자세히 설명했습니다.

그런 다음 소스를 수정 i = 1하고 동일한 분석을 수행하면 다음과 같은 결론을 내립니다.

  • static int i = 0 계속 .bss
  • static int i = 1 계속 .data


6

사용중인 플랫폼과 컴파일러에 따라 다릅니다. 일부 컴파일러는 코드 세그먼트에 직접 저장됩니다. 정적 변수는 항상 현재 번역 단위에만 액세스 할 수 있으며 이름은 내 보내지 않으므로 이름 충돌이 발생하지 않습니다.


5

컴파일 단위로 선언 된 데이터는 해당 파일 출력의 .BSS 또는 .Data로 이동합니다. DATA에서 초기화되지 않은 BSS에서 초기화 된 데이터.

정적 데이터와 전역 데이터의 차이점은 파일에 기호 정보를 포함시키는 데 있습니다. 컴파일러는 심볼 정보를 포함하지만 전역 정보 만 표시하는 경향이 있습니다.

링커는이 정보를 존중합니다. 정적 변수에 대한 기호 정보는 버려지거나 맹 글링되어 정적 변수가 어떤 방식 으로든 디버그 또는 기호 옵션을 사용하여 참조 될 수 있습니다. 어떤 경우에도 링커가 로컬 참조를 먼저 확인하므로 컴파일 단위에 영향을 줄 수 없습니다.


3

나는 objdump와 gdb로 시도했다. 결과는 다음과 같다.

(gdb) disas fooTest
Dump of assembler code for function fooTest:
   0x000000000040052d <+0>: push   %rbp
   0x000000000040052e <+1>: mov    %rsp,%rbp
   0x0000000000400531 <+4>: mov    0x200b09(%rip),%eax        # 0x601040 <foo>
   0x0000000000400537 <+10>:    add    $0x1,%eax
   0x000000000040053a <+13>:    mov    %eax,0x200b00(%rip)        # 0x601040 <foo>
   0x0000000000400540 <+19>:    mov    0x200afe(%rip),%eax        # 0x601044 <bar.2180>
   0x0000000000400546 <+25>:    add    $0x1,%eax
   0x0000000000400549 <+28>:    mov    %eax,0x200af5(%rip)        # 0x601044 <bar.2180>
   0x000000000040054f <+34>:    mov    0x200aef(%rip),%edx        # 0x601044 <bar.2180>
   0x0000000000400555 <+40>:    mov    0x200ae5(%rip),%eax        # 0x601040 <foo>
   0x000000000040055b <+46>:    mov    %eax,%esi
   0x000000000040055d <+48>:    mov    $0x400654,%edi
   0x0000000000400562 <+53>:    mov    $0x0,%eax
   0x0000000000400567 <+58>:    callq  0x400410 <printf@plt>
   0x000000000040056c <+63>:    pop    %rbp
   0x000000000040056d <+64>:    retq   
End of assembler dump.

(gdb) disas barTest
Dump of assembler code for function barTest:
   0x000000000040056e <+0>: push   %rbp
   0x000000000040056f <+1>: mov    %rsp,%rbp
   0x0000000000400572 <+4>: mov    0x200ad0(%rip),%eax        # 0x601048 <foo>
   0x0000000000400578 <+10>:    add    $0x1,%eax
   0x000000000040057b <+13>:    mov    %eax,0x200ac7(%rip)        # 0x601048 <foo>
   0x0000000000400581 <+19>:    mov    0x200ac5(%rip),%eax        # 0x60104c <bar.2180>
   0x0000000000400587 <+25>:    add    $0x1,%eax
   0x000000000040058a <+28>:    mov    %eax,0x200abc(%rip)        # 0x60104c <bar.2180>
   0x0000000000400590 <+34>:    mov    0x200ab6(%rip),%edx        # 0x60104c <bar.2180>
   0x0000000000400596 <+40>:    mov    0x200aac(%rip),%eax        # 0x601048 <foo>
   0x000000000040059c <+46>:    mov    %eax,%esi
   0x000000000040059e <+48>:    mov    $0x40065c,%edi
   0x00000000004005a3 <+53>:    mov    $0x0,%eax
   0x00000000004005a8 <+58>:    callq  0x400410 <printf@plt>
   0x00000000004005ad <+63>:    pop    %rbp
   0x00000000004005ae <+64>:    retq   
End of assembler dump.

여기는 objdump 결과입니다

Disassembly of section .data:

0000000000601030 <__data_start>:
    ...

0000000000601038 <__dso_handle>:
    ...

0000000000601040 <foo>:
  601040:   01 00                   add    %eax,(%rax)
    ...

0000000000601044 <bar.2180>:
  601044:   02 00                   add    (%rax),%al
    ...

0000000000601048 <foo>:
  601048:   0a 00                   or     (%rax),%al
    ...

000000000060104c <bar.2180>:
  60104c:   14 00                   adc    $0x0,%al

즉, 네 개의 변수는 동일한 이름이지만 오프셋이 다른 데이터 섹션 이벤트에 있습니다.


그보다 훨씬 더 있습니다. 기존 답변조차 완전하지 않습니다. 스레드 로컬 스라는 다른 것을 언급하면됩니다.
Adriano Repetti

2

앞에서 언급 한대로 데이터 세그먼트 또는 코드 세그먼트에 저장된 정적 변수.
스택 또는 힙에 할당되지 않도록 할 수 있습니다. 키워드는 변수의 범위를 파일 또는 함수로 정의하기
때문에 충돌의 위험이 없습니다. 충돌의 static경우 경고 할 컴파일러 / 링커가 있습니다.
좋은



1

대답은 컴파일러에 따라 매우 달라질 수 있으므로 질문을 편집하고 싶을 수도 있습니다. 세그먼트의 개념조차도 ISO C 또는 ISO C ++에서 요구하지 않습니다. 예를 들어, Windows에서는 실행 파일에 심볼 이름이 없습니다. 하나의 'foo'는 0x100으로 오프셋되고 다른 하나는 0x2B0으로, 두 번역 단위의 코드는 "그들의"foo에 대한 오프셋을 알고 컴파일됩니다.


0

둘 다 독립적으로 저장되지만 다른 개발자에게 명확하게 표시하려면 네임 스페이스로 묶을 수 있습니다.


-1

초기화되지 않은 데이터 세그먼트 또는 초기화 된 데이터 세그먼트라고도하는 bss (기호로 시작)에 저장되어 있다는 것을 이미 알고 있습니다.

간단한 예를 들어 보자

void main(void)
{
static int i;
}

위의 정적 변수는 초기화되지 않았으므로 초기화되지 않은 데이터 세그먼트 (bss)로 이동합니다.

void main(void)
{
static int i=10;
}

물론 그것은 10으로 초기화되어 초기화 된 데이터 세그먼트로 이동합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.