how does .bss segment allocates and deallocates the memory for the C program?

Viewed 94

I am trying to understand the logic behind the .bss memory allocation and deallocation

I have few cases which I have tried to understand the same.

I concluded that it is incrementing in the chunks of 8 bytes if I declare any variable in the global scope of the code because of the linker script of mine is having the alignment of 8 bytes

but when I define the variable inside the same scope it is deallocating the memory from the .bss

but exactly what is the logical calculations behind the deallocation is which I do not understand?

case1 :

#include <stdio.h>

int a;
int b;

int main(void){
        return 0;
}

this code snippet generates the below sizes for the different segments

   text    data     bss     dec     hex filename
   1418     544      16    1978     7ba a.out

now if I initialize one int variable like this

case 2:

#include <stdio.h>

int a = 1;
int b;

int main(void){
        return 0;
}

this gives me the following size table

   text    data     bss     dec     hex filename
   1418     548      12    1978     7ba a.out

now if I initialize one char before the int variable like this

case 3:

#include <stdio.h>

char a = 1;
int b;

int main(void){
        return 0;
}

it is showing

text    data     bss     dec     hex filename
1418     545      12    1978     7ba a.out

this is the nm -n output specifically for the case 3

0000000000004000 D __data_start
0000000000004000 W data_start
0000000000004008 D __dso_handle
0000000000004010 D a
0000000000004011 B __bss_start
0000000000004011 D _edata
0000000000004014 b completed.8060
0000000000004018 D __TMC_END__
0000000000004018 B b
0000000000004020 B _end

now if I initialize one int variable like this

case 4:

#include <stdio.h>

int a = 1;
char b;

int main(void){
        return 0;
}

it is showing

text    data     bss     dec     hex filename
1418     548      4    1978     7ba a.out

now if I initialize one int variable like this

case 5:

#include <stdio.h>

char a = 1;
char b;

int main(void){
        return 0;
}

it is showing

text    data     bss     dec     hex filename
1418     545      7    1978     7ba a.out

anyone please help me to understand this ?

1 Answers

Symbols of a given type have a natural alignment that the linker must honor (e.g. int must be 4 byte aligned). Within a given section (e.g. .bss), the linker has some leeway to reorder the symbols so that the alignment of a given symbol doesn't cause excess padding. If we have:

char a;
int b;

Without the reordering, the load map would look like:

0000 a
0004 b

And, the section length would be 8.

With reordering:

0000 b
0000 a

And, the section length would be 5

Note that the size of the sections is aligned upward to some multiple (usually 8 bytes). So, for example, if a section has only 1 byte of data, the section size will be reported as 8.

But, there are exceptions.

One of the ways to see this more clearly is to simplify the data:

  1. We can elide any libraries or startup code to reduce the executable size.

  2. We can use addresses relative to the start of the first data section (e.g. start of .data)

  3. Combine results from all cases into a single table

I've created a [perl] script to do that. It has your original test cases and some additional ones.

Of particular note is case 6 ...

Below is the output of the script. The final table [and discussion] is in the FINAL section below.


Case 1:

Source:

int a;
int b;
int _start(void) { return 0; }

Command: nm -n xfile

0000 B b
0000 B __bss_start
0000 B _edata
0004 B a
0008 B _end

Command: size xfile

   text    data     bss     dec     hex filename
     50       0       8      58      3a xfile

Case 2:

Source:

int a = 1;
int b;
int _start(void) { return 0; }

Command: nm -n xfile

0000 D a
0004 B b
0004 B __bss_start
0004 D _edata
0008 B _end

Command: size xfile

   text    data     bss     dec     hex filename
     50       4       4      58      3a xfile

Case 3:

Source:

char a = 1;
int b;
int _start(void) { return 0; }

Command: nm -n xfile

0000 D a
0001 B __bss_start
0001 D _edata
0004 B b
0008 B _end

Command: size xfile

   text    data     bss     dec     hex filename
     50       1       4      55      37 xfile

Case 4:

Source:

int a = 1;
char b;
int _start(void) { return 0; }

Command: nm -n xfile

0000 D a
0004 B b
0004 B __bss_start
0004 D _edata
0008 B _end

Command: size xfile

   text    data     bss     dec     hex filename
     50       4       4      58      3a xfile

Case 5:

Source:

char a = 1;
char b;
int _start(void) { return 0; }

Command: nm -n xfile

0000 D a
0001 B b
0001 B __bss_start
0001 D _edata
0008 B _end

Command: size xfile

   text    data     bss     dec     hex filename
     50       1       7      58      3a xfile

Case 6:

Source:

char a;
int b;
char c = 1;
int d = 1;
int _start(void) { return 0; }

Command: nm -n xfile

0000 D d
0004 D c
0005 B __bss_start
0005 D _edata
0008 B b
000C B a
0010 B _end

Command: size xfile

   text    data     bss     dec     hex filename
     50       5       8      63      3f xfile

FINAL:

CASE    DATA    BSS     DEFS
1       0       8       int a; int b;
                        00/B/b 00/B/__bss_start 00/B/_edata 04/B/a 08/B/_end
2       4       4       int a = 1; int b;
                        00/D/a 04/B/b 04/B/__bss_start 04/D/_edata 08/B/_end
3       1       4       char a = 1; int b;
                        00/D/a 01/B/__bss_start 01/D/_edata 04/B/b 08/B/_end
4       4       4       int a = 1; char b;
                        00/D/a 04/B/b 04/B/__bss_start 04/D/_edata 08/B/_end
5       1       7       char a = 1; char b;
                        00/D/a 01/B/b 01/B/__bss_start 01/D/_edata 08/B/_end
6       5       8       char a; int b; char c = 1; int d = 1;
                        00/D/d 04/D/c 05/B/__bss_start 05/D/_edata 08/B/b 0C/B/a 10/B/_end

Note that in case 6, the .data length is 5. And, the starting address of .bss is [also] 5. But, the lowest .bss address actually used is 8 (for b)


Here is the script source:

#!/usr/bin/perl
# bssgen -- generate .bss data
#
# options:
#   "-c" -- use .o instead of executable
#   "-O" -- optimzation level (DEFAULT: 2)
#   "-v" -- output to stdout (DEFAULT: off)

master(@ARGV);
exit(0);

sub master
{
    my(@argv) = @_;

    $opt_c = 0;
    $opt_O = 2;

    $pubfile = "bssgen.txt";
    printf("output to %s\n",$pubfile);
    $xfpub = xfopen(">$pubfile","master");

    optget(\@argv,
        ["c",1],
        ["n",1],
        ["O",1]);

    my($xfsrc) = "bssgen::DATA";
    $xfsrc = \*$xfsrc;

    while ($bf = <$xfsrc>) {
        chomp($bf);

        if ($bf =~ /^\s*$/) {
            dotest();
        }
        else {
            push(@defs,$bf);
        }
    }

    dotest()
        if (@defs > 0);

    final();

    $xfpub = xfclose($xfpub);
}

sub final
{

    prtsct("FINAL:");

    prtsep();

    @prtcol_tabs = (0,8,16,24);

    prtcol("%s","CASE");
    prtcol("%s","DATA");
    prtcol("%s","BSS");
    prtcol("%s","DEFS");
    prtcol();

    $caseno = 0;
    foreach $tst (@tstlist) {
        ++$caseno;

        prtcol("%d",$caseno);
        prtcol("%d",$tst->{tst_data});
        prtcol("%d",$tst->{tst_bss});
        prtcol("%s",$tst->{tst_defs});
        prtcol();

        prtcol("%s");
        prtcol("%s");
        prtcol("%s");

        my($buf);
        my($nmlhs) = $tst->{tst_nm};
        my($base);
        foreach $nm (@$nmlhs) {
            $buf .= " "
                if (defined($buf));
            $buf .= sprintf("%2.2X/%s/%s",
                $nm->{addr},$nm->{typ},$nm->{sym});
        }
        prtcol("%s",$buf);
        prtcol();
    }

    prtsep();
}

sub dotest
{

    prtsct("Case %d:",++$caseno);

    my($root) = "xfile";
    my($sfile) = $root . ".c";
    my($ofile) = $root . ".o";
    my($xfile) = $root;
    my($run) = $root;

    local($xfdst) = xfopen(">$sfile","dotest/sfile");

    prtpub("Source:\n");
    prtsep();
    foreach $bf (@defs) {
        prtcode("%s\n",$bf);
    }
    prtcode("int %s(void) { return 0; }\n",$opt_c ? "main" : "_start");
    prtsep();

    $xfdst = xfclose($xfdst);

    my(@cflags);
    push(@cflags,"-O")
        if ($opt_O);
    push(@cflags,"-c");
    doexec("cc",@cflags,$sfile);

    unless ($opt_c) {
        doexec("ld","-o",$xfile,$ofile);
        $run = $xfile;
    }
    else {
        $run = $ofile;
    }

    my(@nm);
    push(@nm,"-n");
    @nmrhs = grab(0,"nm",@nm,$run);
    my($base);
    my(@nmlhs);
    prtsep();
    foreach $nm (@nmrhs) {
        chomp($nm);

        my($addr,$typ,$sym) = split(" ",$nm);
        next if ($typ eq "T");

        $addr = hex("0x$addr");
        $base //= $addr;

        $nm = {};
        $nm->{addr} = $addr - $base;
        $nm->{typ} = $typ;
        $nm->{sym} = $sym;

        prtpub("%4.4X %s %s\n",$nm->{addr},$nm->{typ},$nm->{sym});

        push(@nmlhs,$nm);
    }
    prtsep();

    my(@siz) = grab(1,"size",$run);
    my($txt,$data,$bss) = split(" ",$siz[1]);

    my($tst) = {};
    $tst->{tst_defs} = join(" ",@defs);
    $tst->{tst_data} = $data + 0;
    $tst->{tst_bss} = $bss + 0;;
    $tst->{tst_nm} = \@nmlhs;
    push(@tstlist,$tst);

    unless ($opt_k) {
        unlink($sfile);
        unlink($ofile);
        unlink($xfile);
    }

    undef(@defs);
}

sub xfopen
{
    my($file,$who) = @_;
    my($xf);

    open($xf,$file) or
        die("xfopen: unable to open '$file' -- $!\n");

    $xf;
}

sub xfclose
{
    my($xf) = @_;

    close($xf);
    undef($xf);

    $xf;
}

sub grab
{
    my($vflg,@argv) = @_;
    my($cmd,@cmd);

    $cmd = $argv[0];

    prtpub("\n");
    $cmd = join(" ",@argv);
    prtpub("Command: `%s`\n",$cmd);

    @cmd = (`$cmd`);

    prtsep()
        if ($vflg);
    foreach $cmd (@cmd) {
        chomp($cmd);
        prtpub("%s\n",$cmd)
            if ($vflg);
    }
    prtsep()
        if ($vflg);

    @cmd;
}

sub doexec
{

    $_ = join(" ",@_);
    system($_);

    $_ = $? >> 8;
    exit($_) if ($_);
}

sub prtsct
{
    my($fmt,$arg) = @_;

    prtpub("\n");
    prtpub("%s\n","-" x 8);

    $fmt = "**" . $fmt . "**\n";

    prtpub($fmt,$arg);

    prtpub("\n");
}

sub prtsep
{

    prtpub("```\n");
}

sub prtcol
{
    my($fmt,$val) = @_;
    my($tabto);

    {
        unless (defined($fmt)) {
            prtpub("%s\n",$prtcol_buf);
            undef($prtcol_buf);
            undef($prtcol_idx);
            last;
        }

        $tabto = $prtcol_tabs[$prtcol_idx++];
        while (length($prtcol_buf) < $tabto) {
            $prtcol_buf .= " ";
        }

        $fmt = sprintf($fmt,$val);
        $prtcol_buf .= $fmt;
    }
}

sub prtpub
{
    my($fmt);

    $fmt = shift(@_);
    printf($xfpub $fmt,@_);
    printf($fmt,@_)
        if ($opt_v);
}

sub prtcode
{
    my($fmt);

    $fmt = shift(@_);
    printf($xfdst $fmt,@_);
    prtpub($fmt,@_);
}

sub optget
{
    my($argv,@opts) = @_;
    my($arg,$opt);
    my($sym,$val);

    @opts = sort({ $b->[0] cmp $a->[0]} @opts);

    while (@$argv > 0) {
        $arg = $argv->[0];
        last unless ($arg =~ s/^-//);

        shift(@$argv);

        foreach $opt (@opts) {
            my($sym,$dft) = @$opt;

            if ($arg =~ /^$sym(.*)$/) {
                $val = $1;

                {
                    last if ($val =~ s/^=//);
                    last if ($val ne "");
                    $val = $dft;
                }

                ${"opt_" . $sym} = $val;

                last;
            }
        }
    }

    foreach $opt (@opts) {
        my($sym,$dft) = @$opt;
        $sym = "opt_" . $sym;
        ###printf("optget: DEBUG_CAE %s %s\n",$sym,$$sym);
    }
}

package bssgen;
__DATA__
int a;
int b;

int a = 1;
int b;

char a = 1;
int b;

int a = 1;
char b;

char a = 1;
char b;

char a;
int b;
char c = 1;
int d = 1;
Related