Các biến tĩnh được lưu trữ trong C và C ++ ở đâu?


180

Trong phân đoạn nào (.BSS, .DATA, khác) của tệp thực thi là các biến tĩnh được lưu trữ để chúng không có xung đột tên? Ví dụ:


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);
}                              }

Nếu tôi biên dịch cả hai tệp và liên kết nó với một tệp chính gọi fooTest () và barTest nhiều lần, các câu lệnh printf sẽ tăng độc lập. Có ý nghĩa vì các biến foo và bar là cục bộ của đơn vị dịch.

Nhưng lưu trữ được phân bổ ở đâu?

Để rõ ràng, giả định là bạn có một chuỗi công cụ sẽ xuất ra một tệp ở định dạng ELF. Vì vậy, tôi tin rằng có được một số không gian dành trong file thực thi cho các biến tĩnh.
Đối với mục đích thảo luận, giả sử chúng tôi sử dụng chuỗi công cụ GCC.


1
Hầu hết mọi người đang nói với bạn rằng họ nên được lưu trữ trong phần .DATA thay vì trả lời câu hỏi của bạn: chính xác ở đâu trong phần .DATA và làm thế nào bạn có thể tìm thấy ở đâu. Tôi thấy bạn đã đánh dấu một câu trả lời, vì vậy bạn đã biết làm thế nào để tìm thấy nó?
lukmac

tại sao khởi tạo và chưa khởi tạo được đặt trong các phần khác nhau: linuxjournal.com/article/1059
mhk

1
Lưu trữ được phân bổ cho các biến toàn cục / tĩnh của bạn khi chạy không liên quan gì đến độ phân giải tên của chúng, điều này xảy ra trong thời gian xây dựng / liên kết. Sau khi thực thi đã được xây dựng - không còn tên nữa.
valdo

2
Câu hỏi này là vô nghĩa, được xây dựng dựa trên tiền đề sai lầm rằng "va chạm tên" của các biểu tượng không được quan tâm là một điều có thể tồn tại. Thực tế là không có câu hỏi chính đáng nào có thể giải thích mức độ nghiêm trọng của một số câu trả lời. Thật khó tin vì vậy rất ít người có được điều này.
gạch dưới

Câu trả lời:


131

Số liệu thống kê của bạn đi đâu phụ thuộc vào việc chúng có được khởi tạo không . Dữ liệu tĩnh được khởi tạo bằng không đi vào .BSS (Khối bắt đầu bằng ký hiệu) , dữ liệu không khởi tạo bằng không đi vào .DATA


50
Với "không khởi tạo 0", bạn có thể có nghĩa là "khởi tạo, nhưng với một cái gì đó khác 0". Bởi vì không có thứ gọi là dữ liệu tĩnh "không khởi tạo" trong C / C ++. Mọi thứ tĩnh đều được khởi tạo bằng không theo mặc định.
AnT

21
@Don Neufeld: câu trả lời của bạn hoàn toàn không trả lời câu hỏi. Tôi không hiểu tại sao nó được chấp nhận. Bởi vì cả 'foo' và 'bar' đều không được khởi tạo. Câu hỏi đặt ra là nơi đặt hai biến tĩnh / toàn cầu có cùng tên trong .bss hoặc .data
lukmac 20/03/2016

Tôi đã sử dụng các triển khai trong đó dữ liệu tĩnh được khởi tạo bằng 0 rõ ràng .datavà dữ liệu tĩnh không có trình khởi tạo đi vào .bss.
MM

1
@MM Trong trường hợp của tôi, liệu thành viên tĩnh không được khởi tạo (được khởi tạo ngầm thành 0) hoặc được khởi tạo rõ ràng thành 0, trong cả hai trường hợp, nó được thêm vào trong phần .bss.
cbinder

Là thông tin này cụ thể cho một loại tập tin thực thi nhất định? Tôi giả sử, vì bạn không chỉ định, rằng nó áp dụng ít nhất cho các tệp thực thi ELF và Windows PE, nhưng còn các loại khác thì sao?
Jerry Jeremiah

116

Khi một chương trình được tải vào bộ nhớ, nó được tổ chức thành các phân đoạn khác nhau. Một trong những phân khúc là phân khúc DATA . Phân đoạn dữ liệu được chia nhỏ thành hai phần:

Phân đoạn dữ liệu khởi tạo: Tất cả dữ liệu toàn cầu, tĩnh và không đổi được lưu trữ tại đây.
Phân đoạn dữ liệu chưa được khởi tạo (BSS): Tất cả các dữ liệu chưa được khởi tạo được lưu trữ trong phân khúc này.

Dưới đây là một sơ đồ để giải thích khái niệm này:

nhập mô tả hình ảnh ở đây


đây là liên kết rất tốt để giải thích các khái niệm này:

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


Câu trả lời ở trên cho biết 0 khởi tạo đi vào BSS. Có 0 khởi tạo có nghĩa là chưa được khởi tạo hoặc 0 mỗi se? Nếu nó có nghĩa là 0 mỗi se thì tôi nghĩ bạn nên đưa nó vào câu trả lời của bạn.
Viraj

Dữ liệu không đổi không được lưu trữ trong phân đoạn .data mà trong phân đoạn .const của phần văn bản.
dùng10678

Thay vì điều này (" Phân đoạn dữ liệu khởi tạo : Tất cả dữ liệu toàn cầu, tĩnh và không đổi được lưu trữ ở đây. Phân đoạn dữ liệu chưa được khởi tạo (BSS) : Tất cả dữ liệu chưa được khởi tạo được lưu trữ trong phân đoạn này."), Tôi nghĩ rằng nó nên nói điều này: (" Phân đoạn dữ liệu khởi tạo : Tất cả các biến toàn cục & tĩnh được khởi tạo thành giá trị khác không và tất cả dữ liệu không đổi, được lưu trữ tại đây. Phân đoạn dữ liệu chưa được khởi tạo (BSS) : Tất cả các biến toàn cục và biến tĩnh không được khởi tạo hoặc khởi tạo về không, được lưu trữ trong phân khúc này. ").
Gabriel Staples

Cũng lưu ý rằng theo như tôi hiểu, "dữ liệu khởi tạo" có thể bao gồm các biến hằng được khởi tạo . Trên một vi điều khiển (ví dụ: STM32), các biến được khởi tạo được lưu theo mặc định trong bộ nhớ Flashđược sao chép vào RAM khi khởi động và các hằng số khởi tạo được để lại và chỉ được đọc từ Flash , cùng với văn bản chứa chương trình chính nó, và chỉ còn lại trong Flash.
Gabriel Staples

Vì vậy, những gì tôi thu thập được từ sơ đồ này là các biến toàn cục hoặc tĩnh (vì các biến tĩnh hoạt động giống như các biến toàn cục trong thời gian) không nằm trong heap hay stack, mà được phân bổ trên bộ nhớ ngoài cả hai biến đó. Có đúng không? Tôi cho rằng tôi có thể xem lại tập lệnh liên kết STM32 để nghiên cứu phân bổ bộ nhớ nhiều hơn.
Gabriel Staples

32

Trong thực tế, một biến là tuple (lưu trữ, phạm vi, loại, địa chỉ, giá trị):

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

Phạm vi cục bộ có thể có nghĩa là cục bộ đối với đơn vị tịnh tiến (tệp nguồn), hàm hoặc khối tùy thuộc vào vị trí được xác định. Để hiển thị biến cho nhiều hơn một hàm, nó chắc chắn phải nằm trong vùng DATA hoặc BSS (tùy thuộc vào việc nó được khởi tạo rõ ràng hay không, tương ứng). Sau đó, phạm vi của nó phù hợp với tất cả (các) chức năng hoặc (các) chức năng trong tệp nguồn.


21

Vị trí lưu trữ của dữ liệu sẽ phụ thuộc vào việc thực hiện.

Tuy nhiên, ý nghĩa của tĩnh là "liên kết nội bộ". Do đó, biểu tượng là nội bộ của đơn vị biên dịch (foo.c, bar.c) và không thể được tham chiếu bên ngoài đơn vị biên dịch đó. Vì vậy, không thể có sự va chạm tên.


Không. thế giới khóa tĩnh có ý nghĩa quá tải: trong trường hợp như vậy tĩnh là công cụ sửa đổi lưu trữ, không phải là công cụ sửa đổi liên kết.
ugasoft

4
ugasoft: các statics bên ngoài chức năng là các bộ điều chỉnh liên kết, bên trong là các bộ điều chỉnh lưu trữ, nơi không thể có xung đột để bắt đầu.
wnoise

12

trong khu vực "toàn cầu và tĩnh" :)

Có một số vùng nhớ trong C ++:

  • đống
  • cửa hàng miễn phí
  • cây rơm
  • toàn cầu và tĩnh
  • hăng sô

Xem ở đây để trả lời chi tiết cho câu hỏi của bạn:

Phần sau đây tóm tắt các vùng nhớ phân biệt chính của chương trình C ++. Lưu ý rằng một số tên (ví dụ: "heap") không xuất hiện như vậy trong dự thảo [tiêu chuẩn].

     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

Tôi không tin sẽ có một vụ va chạm. Sử dụng tĩnh ở cấp độ tệp (các hàm bên ngoài) đánh dấu biến là cục bộ cho đơn vị biên dịch hiện tại (tệp). Nó không bao giờ hiển thị bên ngoài tệp hiện tại vì vậy không bao giờ phải có tên có thể được sử dụng bên ngoài.

Sử dụng tĩnh bên trong một hàm là khác nhau - biến chỉ hiển thị cho hàm (dù tĩnh hay không), giá trị của nó chỉ được giữ nguyên qua các lệnh gọi đến hàm đó.

Trong thực tế, tĩnh thực hiện hai điều khác nhau tùy thuộc vào vị trí của nó. Trong cả hai tuy nhiên trường hợp này, khả năng hiển thị biến được giới hạn trong một cách mà bạn có thể dễ dàng ngăn chặn đụng độ namespace khi liên kết.

Có nói rằng, tôi tin rằng nó sẽ được lưu trữ trong DATAphần này, có xu hướng có các biến được khởi tạo thành các giá trị khác không. Tất nhiên, đây là một chi tiết triển khai, không phải là thứ bắt buộc theo tiêu chuẩn - nó chỉ quan tâm đến hành vi, chứ không phải cách mọi thứ được thực hiện dưới vỏ bọc.


1
@paxdiablo: bạn đã đề cập đến hai loại biến tĩnh. Bài viết nào trong số này mà bài viết này ( en.wikipedia.org/wiki/Data_seribution ) đề cập đến? Phân đoạn dữ liệu cũng chứa các biến toàn cục (hoàn toàn trái ngược về bản chất với các biến tĩnh). 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, nó haas để làm với tầm nhìn. Có thể có những thứ được lưu trữ trong một phân đoạn cục bộ cho một đơn vị biên dịch, những thứ khác có thể truy cập đầy đủ. Một ví dụ: nghĩ về mỗi đơn vị comp đóng góp một khối cho phân đoạn DATA. Nó biết mọi thứ ở đâu trong khối đó. Nó cũng xuất bản địa chỉ của những thứ đó trong khối mà nó muốn các đơn vị đồng hành khác có quyền truy cập. Trình liên kết có thể giải quyết các địa chỉ đó tại thời điểm liên kết.
paxdiablo

11

Làm thế nào để tìm thấy nó với objdump -Sr

Để thực sự hiểu những gì đang xảy ra, bạn phải hiểu di dời liên kết. Nếu bạn chưa bao giờ chạm vào điều đó, hãy xem xét việc đọc bài viết này trước .

Hãy phân tích một ví dụ ELF Linux x86-64 để tự mình xem nó:

#include <stdio.h>

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

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

Biên dịch với:

gcc -ggdb -c main.c

Dịch ngược mã với:

objdump -Sr main.o
  • -S dịch ngược mã với nguồn gốc xen kẽ
  • -r hiển thị thông tin di dời

Bên trong sự dịch ngược của fchúng ta thấy:

 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-0x4nói rằng nó sẽ đi đến byte đầu tiên của .dataphân khúc.

-0x4bởi vì chúng tôi đang sử dụng địa chỉ tương đối RIP, do đó, %riptrong hướng dẫn và R_X86_64_PC32.

Điều này là bắt buộc vì RIP trỏ đến hướng dẫn sau , bắt đầu 4 byte sau 00 00 00 00đó là những gì sẽ được di dời. Tôi đã giải thích điều này chi tiết hơn tại: https://stackoverflow.com/a/30515926/895245

Sau đó, nếu chúng tôi sửa đổi nguồn thành i = 1và thực hiện phân tích tương tự, chúng tôi kết luận rằng:

  • static int i = 0 tiếp tục .bss
  • static int i = 1 tiếp tục .data


6

Nó phụ thuộc vào nền tảng và trình biên dịch mà bạn đang sử dụng. Một số trình biên dịch lưu trữ trực tiếp trong đoạn mã. Các biến tĩnh luôn chỉ có thể truy cập được đối với đơn vị dịch hiện tại và các tên không được xuất do đó các xung đột tên lý do không bao giờ xảy ra.


5

Dữ liệu được khai báo trong một đơn vị biên dịch sẽ đi vào .BSS hoặc .Data của đầu ra tệp đó. Dữ liệu ban đầu trong BSS, không được cung cấp trong DATA.

Sự khác biệt giữa dữ liệu tĩnh và toàn cầu xuất hiện trong việc bao gồm thông tin ký hiệu trong tệp. Trình biên dịch có xu hướng bao gồm thông tin biểu tượng nhưng chỉ đánh dấu thông tin toàn cầu như vậy.

Trình liên kết tôn trọng thông tin này. Thông tin ký hiệu cho các biến tĩnh được loại bỏ hoặc xử lý để các biến tĩnh vẫn có thể được tham chiếu theo một cách nào đó (với các tùy chọn gỡ lỗi hoặc ký hiệu). Trong cả hai trường hợp, các đơn vị biên dịch có thể bị ảnh hưởng vì trình liên kết giải quyết các tham chiếu cục bộ trước tiên.


3

Tôi đã thử nó với objdump và gdb, đây là kết quả tôi nhận được:

(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.

đây là kết quả 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

Vì vậy, có nghĩa là, bốn biến của bạn được đặt trong phần dữ liệu cùng tên, nhưng có độ lệch khác nhau.


Có nhiều hơn thế. Ngay cả câu trả lời hiện tại không đầy đủ. Chỉ cần đề cập đến một cái gì đó khác: chủ đề địa phương.
Adriano Repetti

2

biến tĩnh được lưu trữ trong phân đoạn dữ liệu hoặc phân đoạn mã như đã đề cập trước đó.
Bạn có thể chắc chắn rằng nó sẽ không được phân bổ trên stack hoặc heap.
Không có rủi ro xung đột vì statictừ khóa xác định phạm vi của biến là tệp hoặc hàm, trong trường hợp va chạm, có trình biên dịch / liên kết để cảnh báo bạn về.
Một ví dụ điển hình



1

Câu trả lời rất có thể phụ thuộc vào trình biên dịch, vì vậy bạn có thể muốn chỉnh sửa câu hỏi của mình (ý tôi là, ngay cả khái niệm phân đoạn cũng không bắt buộc bởi ISO C hay ISO C ++). Chẳng hạn, trên Windows, một tệp thực thi không mang tên biểu tượng. Một 'foo' sẽ được bù 0x100, còn lại có lẽ là 0x2B0 và mã từ cả hai đơn vị dịch được biên dịch khi biết các giá trị bù cho "foo" của họ.


0

cả hai sẽ được lưu trữ độc lập, tuy nhiên nếu bạn muốn làm rõ cho các nhà phát triển khác, bạn có thể muốn gói chúng trong các không gian tên.


-1

bạn đã biết hoặc nó lưu trữ trong bss (khối bắt đầu bằng ký hiệu) cũng được gọi là phân đoạn dữ liệu chưa được khởi tạo hoặc trong phân đoạn dữ liệu khởi tạo.

hãy lấy một ví dụ đơn giản

void main(void)
{
static int i;
}

biến tĩnh ở trên không được khởi tạo, vì vậy nó đi đến phân đoạn dữ liệu chưa được khởi tạo (bss).

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

và tất nhiên nó được khởi tạo bởi 10 vì vậy nó đi đến phân đoạn dữ liệu được khởi tạo.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.