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:
We can elide any libraries or startup code to reduce the executable size.
We can use addresses relative to the start of the first data section (e.g. start of .data)
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;