C言語のメモリ環境について

目次
1.メモリ領域の違い
1.1 各メモリの割り当て状況を確認する
2.スタックメモリの確認方法
3.ヒープメモリの確認
(1)動的メモリのデバッグツール:Valgrind(ヴァルグリンド、GNU General Public License)
(2)プロセスのメモリマッピングの確認
(3)/procファイルシステムの使用
(4)ランタイムライブラリの関数
4.Linuxのメモリマップ設定

1.メモリ領域の違い

メモリ領域          特徴
スタック領域・関数呼び出しやローカル変数の割り当てに使われる。・後入れ先出し(LIFO)のデータ構造をもつ。
ヒープ領域・プログラマが明示的にメモリの確保(C:malloc 関数、C++:new演算子)と解放(freeなど)を行う必要がある。・動的にメモリを確保・開放するのに適したデータ構造をもつ。
静的領域          ・グローバル変数などの静的変数が置かれる。
テキスト領域・機械語に翻訳されたプログラムが格納され、この機械語の命令が1行づつ実行されることでプログラムが動く。

(参考)
・メモリの4領域
https://brain.cc.kogakuin.ac.jp/~kanamaru/lecture/MP/final/part06/node8.html

1.1 各メモリの割り当て状況を確認する
(1)テキスト領域
 テキスト領域(プログラムコードが格納される領域)は、通常、実行ファイルの読み込みアドレスを確認することで確認できます。以下は、テキスト領域の開始アドレスを取得する方法です。

#include <stdio.h>

void dummy_function() {}

int main() {
    printf("Address of main: %p\n", (void*)main);
    printf("Address of dummy_function: %p\n", (void*)dummy_function);
    return 0;
}
#実行結果
koba@koba-VirtualBox:~/work$ gcc c_memo1.c
koba@koba-VirtualBox:~/work$ ./a.out
Address of main: 0x561e356a2154
Address of dummy_function: 0x561e356a2149

ここで、”(void)main”の “(void)”はvoidは、どんなデータ型にもキャスト可能なポインタ型です。これは、特定のデータ型を指さない汎用ポインタであり、さまざまな型のデータを指すことができます。voidは型情報を持たないため、キャストを間違えると意図しない動作を引き起こす可能性があり、ポインタ演算など行うときは正しいデータ型にキャストするよう注意が必要です。

(2)静的領域
 静的領域(グローバル変数や静的変数が格納される領域)のアドレスを取得するには、静的変数やグローバル変数のアドレスを確認します。

#include <stdio.h>

int global_var = 0;
static int static_var = 0;

int main() {
    printf("Address of global_var: %p\n", (void*)&global_var);
    printf("Address of static_var: %p\n", (void*)&static_var);
    return 0;
}
koba@koba-VirtualBox:~/work$ gcc c_memo2.c
koba@koba-VirtualBox:~/work$ ./a.out
Address of global_var: 0x55914cf55014
Address of static_var: 0x55914cf55018

(3)スタック領域
 スタック領域のメモリ割り当ては、ローカル変数のアドレスを確認することで確認できます。

#include <stdio.h>

void dummy_function() {}

int main() {
    printf("Address of main: %p\n", (void*)main);
    printf("Address of dummy_function: %p\n", (void*)dummy_function);
    return 0;
}
#実行結果
koba@koba-VirtualBox:~/work$ gcc c_memo3.c
koba@koba-VirtualBox:~/work$ ./a.out
Address of local_var: 0x7ffd25c10df4

(4)ヒープ領域
 ヒープ領域のメモリ割り当ては、malloc関数で動的に割り当てたメモリのアドレスを確認することで確認できます。

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *heap_var = (int*)malloc(sizeof(int));
    if (heap_var != NULL) {
        printf("Address of heap_var: %p\n", (void*)heap_var);
        free(heap_var);
    }
    return 0;
}
#実行結果
koba@koba-VirtualBox:~/work$ gcc c_memo4.c
koba@koba-VirtualBox:~/work$ ./a.out
Address of heap_var: 0x55dc2abee2a0

各種類の領域をまとめて表示

#include <stdio.h>
#include <stdlib.h>

// グローバル変数
int global_var = 0;
static int static_var = 0;

void dummy_function() {}

int main() {
    // ローカル変数
    int local_var = 0;
    // ヒープ変数
    int *heap_var = (int*)malloc(sizeof(int));
    
    if (heap_var == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        return 1;
    }

    // 各メモリ領域のアドレスを表示
    printf("Address of main: %p (Text Segment)\n", (void*)main);
    printf("Address of dummy_function: %p (Text Segment)\n", (void*)dummy_function);
    printf("Address of global_var: %p (Static Segment)\n", (void*)&global_var);
    printf("Address of static_var: %p (Static Segment)\n", (void*)&static_var);
    printf("Address of local_var: %p (Stack Segment)\n", (void*)&local_var);
    printf("Address of heap_var: %p (Heap Segment)\n", (void*)heap_var);

    // メモリ解放
    free(heap_var);

    return 0;
}
#実行結果
koba@koba-VirtualBox:~/work$ gcc c_memo5.c
koba@koba-VirtualBox:~/work$ ./a.out
Address of main: 0x5629cd6bd1d4 (Text Segment)
Address of dummy_function: 0x5629cd6bd1c9 (Text Segment)
Address of global_var: 0x5629cd6c002c (Static Segment)
Address of static_var: 0x5629cd6c0030 (Static Segment)
Address of local_var: 0x7ffd969119dc (Stack Segment)
Address of heap_var: 0x5629cd9c82a0 (Heap Segment)
Address of main     : 0x5629cd6bd1d4 (Text Segment)  テキスト領域 low address
Address of dummy_function : 0x5629cd6bd1c9 (Text Segment)   〃
Address of global_var   : 0x5629cd6c002c (Static Segment) 静的領域
Address of static_var    : 0x5629cd6c0030 (Static Segment)  〃
Address of heap_var    : 0x5629cd9c82a0 (Heap Segment)  ヒープ領域
Address of local_var: 0x7ffd969119dc (Stack Segment)     スタック領域 high address 

2.スタックメモリの確認方法
(1)GDB (GNU Debugger)の使用

#include <stdio.h>

void func2(int x) {
    int y = x + 2;
    printf("func2: y = %d\n", y);
}

void func1(int a, int b) {
    int c = a + b;
    func2(c);
}

int main() {
    int p = 5;
    int q = 10;
    func1(p, q);
    return 0;
}
#デバッカgdbで実行
// -gオプションを付けてコンパイル
koba@koba-VirtualBox:~/work$ gcc -g c_gdb.c
// gdbデバッカの起動
koba@koba-VirtualBox:~/work$ gdb ./a.out
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./a.out...
(gdb) i b
No breakpoints or watchpoints.
// ブレークポイントの設定
(gdb) break main
Breakpoint 1 at 0x11a4: file c_gdb.c, line 13.
(gdb) break func2
Breakpoint 2 at 0x1149: file c_gdb.c, line 3.
// ブレークポイントの一覧表示
(gdb) i b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00000000000011a4 in main at c_gdb.c:13
2       breakpoint     keep y   0x0000000000001149 in func2 at c_gdb.c:3
// 実行
(gdb) run
Starting program: /home/koba/work/a.out

Breakpoint 1, main () at c_gdb.c:13
13      int main() {
// バックトレースの表示
(gdb) bt
#0  main () at c_gdb.c:13
// プログラムの続行
(gdb) continue
Continuing.

Breakpoint 2, func2 (x=0) at c_gdb.c:3
3       void func2(int x) {
// バックトレースの表示
(gdb) bt
#0  func2 (x=0) at c_gdb.c:3
#1  0x00005555555551a1 in func1 (a=5, b=10) at c_gdb.c:10
#2  0x00005555555551cd in main () at c_gdb.c:16
(gdb) continue
Continuing.
func2: y = 17
// デバックの正常終了
[Inferior 1 (process 3392) exited normally]

(参考)
・gdbのコマンド一覧
https://www.fos.kuis.kyoto-u.ac.jp/le2soft/siryo-html/node49.html

gdbの主なコマンド

コマンド名省略型動作
run [args]rプログラムを(引数argsで)実行する
break nb nブレークポイントの設定、n行目又は関数名
deleated nnのブレークポイントを削除
info breakpointsi bブレークポイントの一覧
continuecプログラムの実行を再開
stepsステップ・イン実行
backtrace btバックトレースの表示
quitqgdbの終了

(2)プロセスリミットの確認
 Unix系OSでは、ulimitコマンドでプロセスのスタックサイズリミットを確認できます。
 ulimit -s コマンドを実行して 8192 と表示される場合、それはスタックサイズの制限をキロバイト単位で表しています。具体的には、現在のシェルセッションにおけるスタックサイズの制限が 8192 キロバイト(8 メガバイト)であることを示します。
 スタックサイズが小さいと、深い再帰や大量のローカル変数を使用するプログラムはスタックオーバーフローを引き起こす可能性があります。

 スタックサイズは次のシェルを実行することで16メガバイトにできます。
 $ ulimit -s 16384
 システム上で永続的に設定するときは、システムの設定ファイル(例えば /etc/security/limits.conf)を変更する必要があります(この設定ファイルには該当箇所はありませんでした)。

3.ヒープメモリの確認
(1)動的メモリのデバッグツール:Valgrind(ヴァルグリンド、GNU General Public License)
 Valgrindは、メモリリークや不正なメモリアクセスを検出するための動的解析ツールの一つです。Valgrindを使用してプログラムを実行すると、ヒープメモリの使用状況が詳細に報告されます。

#Valgrkoba@koba-VirtualBox:~/work$ sudo snap install valgrind --classic
valgrind 3.22.0 from Roger Light (ralight) installedindのインストール
#valgrindの実行結果
koba@koba-VirtualBox:~/work$ valgrind --leak-check=full ./a.out
==3987== Memcheck, a memory error detector
==3987== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==3987== Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
==3987== Command: ./a.out
==3987==
func2: y = 17
==3987==
==3987== HEAP SUMMARY:
==3987==     in use at exit: 0 bytes in 0 blocks
==3987==   total heap usage: 1 allocs, 1 frees, 1,024 bytes allocated
==3987==
==3987== All heap blocks were freed -- no leaks are possible
==3987==
==3987== For lists of detected and suppressed errors, rerun with: -s
==3987== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

①Valgrindの概要
 Memcheck:Valgrindの一部で、メモリエラーの検出を行います。
 Copyright:Valgrindの著作権情報。
 Version:使用しているValgrindのバージョン。
 Command:実行されたコマンド(ここでは ./a.out )。
②プログラムの出力
 プログラム自体の出力でfunc2関数からの出力。
③HEAP SUMMARY(ヒープの概要)
・in use at exit:プログラム終了時に使用中のメモリの量。ここでは、0バイトが0ブロックに使用されていることを示しています。つまり、全てのメモリが解放されています。
・total heap usage:プログラムの実行中に行われたメモリ割り当て(allocs)と解放(frees)の総数、および割り当てられたメモリの総量。
・1 allocs:1回のメモリ割り当てが行われた。
・1 frees:1回のメモリ解放が行われた。
・1,024 bytes allocated:合計で1,024バイトのメモリが割り当てられた。
④メモリリークの確認
・All heap blocks were freed:すべてのヒープメモリブロックが解放されたことを示します。
・no leaks are possible:メモリリークがないことを意味します。
⑤エラーの概要
・0 errors from 0 contexts:検出されたエラーはゼロであることを示します。
・suppressed: 0 from 0:抑制されたエラーはないことを示します。

 このValgrindの出力から、次のことがわかります:
・プログラムは正常に実行され、出力も予期されたものでした。
・メモリ使用に関しては、メモリリークは発生していません。
・割り当てられたメモリはすべて適切に解放されています。
・検出されたメモリエラーはありません。
 この結果から、プログラムはメモリ管理に関して健全であると言えます。Valgrindを使用することで、メモリリークやその他のメモリエラーを検出し、修正することができます。

(参考)
・Valgrind による動的解析
https://qiita.com/QGv/items/d7857cb0fff80ecbe41d#valgrind%E3%81%A8%E3%81%AF

(2)プロセスのメモリマッピングの確認:
 Linuxでは、pmapコマンドを使ってプロセスのメモリマッピングを確認できます。ヒープ領域のサイズと位置を特定することができます。
メモリを動的に割り当てる簡単なCプログラムを用意します。

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *p = (int *)malloc(1024 * sizeof(int));  // 1024個のintを割り当て
    if (p == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        return 1;
    }
    for (int i = 0; i < 1024; i++) {
        p[i] = i;
    }
    printf("Memory allocated and initialized\n");

    // プログラムを一時停止して、pmapコマンドで確認できるようにする
    printf("Press Enter to continue...");
    getchar();

    free(p);  // メモリを解放
    return 0;
}
#実行結果
// -gオプションでコンパイル
$ gcc -g -o c_mem6 c_memo6.c
// c_mem6プロセスIDを調べる
$ ps aux | grep c_memo6
//  c_mem6のプロセスID:2315でpmapコマンド
$ pmap 2315

ヒープ領域の詳細な確認
詳細なメモリ使用情報を表示するために、pmapコマンドに-xオプションを使用

#include <stdio.h>
#include <stdlib.h>

int main() {
    // 10MBのメモリを動的に割り当て
    size_t size = 10 * 1024 * 1024;
    int *p = (int *)malloc(size);
    if (p == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        return 1;
    }

    // メモリを使用
    for (size_t i = 0; i < size / sizeof(int); i++) {
        p[i] = i;
    }

    printf("Memory allocated and initialized. Press Enter to continue...\n");
    getchar(); // ユーザー入力を待つ

    free(p); // メモリを解放
    return 0;
}
#実行結果
// -gオプションでコンパイル
$ gcc -g -o c_heaptest c_heaptest.c
// c_heaptestプロセスIDを調べる
$ ps aux | grep c_heaptest
//  c_heaptestのプロセスID:でpmapコマンド
$ pmap -x 2495

出力の中に、[ heap ] というラベルは表示されない場合、[ anon ] として表示される行がヒープ領域を示すことが多いとのこと。
 上記の例では、
 00007fe5066f0000 10244 10244 10244 rw— [ anon ]
アドレスにある 10MB(10244KB)の匿名メモリがヒープ領域です。

(3)/procファイルシステムの使用:
 /proc/[pid]/maps:
 Linuxでは、/procファイルシステムを使ってプロセスのメモリマップを確認できます。ヒープ領域は通常、heapとして表示されます

(4)ランタイムライブラリの関数:mallinfo:
 mallinfo関数は、ヒープメモリの統計情報を提供します。例えば、以下のコードでヒープメモリの使用状況を表示できます。

#include <malloc.h>
#include <stdio.h>

int main() {
    struct mallinfo mi = mallinfo();
    printf("Total non-mmapped bytes (arena):       %d\n", mi.arena);
    printf("Number of free chunks (ordblks):        %d\n", mi.ordblks);
    printf("Number of free fastbin blocks (smblks): %d\n", mi.smblks);
    printf("Number of mapped regions (hblks):       %d\n", mi.hblks);
    printf("Bytes in mapped regions (hblkhd):       %d\n", mi.hblkhd);
    printf("Max. total allocated space (usmblks):   %d\n", mi.usmblks);
    printf("Free bytes held in fastbins (fsmblks):  %d\n", mi.fsmblks);
    printf("Total allocated space (uordblks):       %d\n", mi.uordblks);
    printf("Total free space (fordblks):            %d\n", mi.fordblks);
    printf("Topmost releasable block (keepcost):    %d\n", mi.keepcost);
    return 0;
}

実行結果

c_heaptestを実行し、入力待ちになっている状態で、mallinfo()を実行

4.Linuxのメモリマップ設定
 Linuxでは、スタックのサイズはulimitコマンドで確認および設定できます

koba@koba-VirtualBox:~/work$ ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 7646
max locked memory       (kbytes, -l) 65536
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 7646
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

・標準のリンカスクリプト
 GCCがデフォルトで使用するリンカスクリプトは、インストールされたツールチェーンのディレクトリにあります。標準スクリプトの場所を見つけるには、以下のコマンドを使用して標準のリンカスクリプトを表示することができます

$ ld --verbose 

(以下、抜粋)
OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64",
	      "elf64-x86-64")
OUTPUT_ARCH(i386:x86-64)
ENTRY(_start)
SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu"); SEARCH_DIR("=/lib/x86_64-linux-gnu"); SEARCH_DIR("=/usr/lib/x86_64-linux-gnu"); SEARCH_DIR("=/usr/lib/x86_64-linux-gnu64"); SEARCH_DIR("=/usr/local/lib64"); SEARCH_DIR("=/lib64"); SEARCH_DIR("=/usr/lib64"); SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib"); SEARCH_DIR("=/usr/x86_64-linux-gnu/lib64"); SEARCH_DIR("=/usr/x86_64-linux-gnu/lib");
SECTIONS
{
   :

実際のプログラムをコンパイルしたとき、どのようにメモリに配置されるか確認します。

koba@koba-VirtualBox:~/work$ objdump -h my_program
my_program:     file format elf64-x86-64
Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .interp       0000001c  0000000000000318  0000000000000318  00000318  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .note.gnu.property 00000020  0000000000000338  0000000000000338  00000338  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .note.gnu.build-id 00000024  0000000000000358  0000000000000358  00000358  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .note.ABI-tag 00000020  000000000000037c  000000000000037c  0000037c  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .gnu.hash     00000024  00000000000003a0  00000000000003a0  000003a0  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  5 .dynsym       000000c0  00000000000003c8  00000000000003c8  000003c8  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  6 .dynstr       00000089  0000000000000488  0000000000000488  00000488  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  7 .gnu.version  00000010  0000000000000512  0000000000000512  00000512  2**1
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  8 .gnu.version_r 00000020  0000000000000528  0000000000000528  00000528  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  9 .rela.dyn     000000c0  0000000000000548  0000000000000548  00000548  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 10 .rela.plt     00000030  0000000000000608  0000000000000608  00000608  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 11 .init         0000001b  0000000000001000  0000000000001000  00001000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 12 .plt          00000030  0000000000001020  0000000000001020  00001020  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 13 .plt.got      00000010  0000000000001050  0000000000001050  00001050  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 14 .plt.sec      00000020  0000000000001060  0000000000001060  00001060  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 15 .text         00000195  0000000000001080  0000000000001080  00001080  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 16 .fini         0000000d  0000000000001218  0000000000001218  00001218  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 17 .rodata       00000027  0000000000002000  0000000000002000  00002000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 18 .eh_frame_hdr 00000044  0000000000002028  0000000000002028  00002028  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 19 .eh_frame     00000108  0000000000002070  0000000000002070  00002070  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 20 .init_array   00000008  0000000000003db0  0000000000003db0  00002db0  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 21 .fini_array   00000008  0000000000003db8  0000000000003db8  00002db8  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 22 .dynamic      000001f0  0000000000003dc0  0000000000003dc0  00002dc0  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 23 .got          00000050  0000000000003fb0  0000000000003fb0  00002fb0  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 24 .data         00000014  0000000000004000  0000000000004000  00003000  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 25 .bss          00000004  0000000000004014  0000000000004014  00003014  2**0
                  ALLOC
 26 .comment      0000002b  0000000000000000  0000000000000000  00003014  2**0
                  CONTENTS, READONLY

ここで、
・VMA(Virtual Memory Address)
 VMAは、セクションが実行時に仮想メモリ空間内で占めるアドレスを示します。プログラムが実行されるときに、CPUがこのアドレスを使用してメモリにアクセスします。
例えば、コードセクション(.text)やデータセクション(.data)の実行時のアドレスはVMAで決まります。VMAは、プログラムが実行されるときに、オペレーティングシステムが各セクションをマッピングするアドレスです。
・LMA(Load Memory Address)
 LMAは、セクションが実際にメモリにロードされるアドレスを示します。特に、初期化データセクション(.data)やBSSセクション(.bss)のようなセクションで重要です。
LMAは、プログラムがディスクからメモリにロードされるときに、セクションが配置される物理メモリのアドレスです。
・VMAとLMAの違い
 VMAは実行時のアドレス、LMAはロード時のアドレスです。
 通常、コードセクション(.text)や読み取り専用データセクション(.rodata)では、VMAとLMAが同じです。しかし、初期化データセクション(.data)や未初期化データセクション(.bss)では異なることがあります。例えば、.dataセクションはROMからRAMにコピーされる場合、ROM上のアドレスがLMAであり、RAM上のアドレスがVMAです。

(参考)
・objdump – オブジェクトファイルの情報を表示する
https://linuxcommand.net/objdump/#_-h

(2)組み込みシステムやカスタムOSでのメモリマップ設定
 組み込みシステムやカスタムOSの場合、メモリマップはリンクスクリプト(例えば、GNU linkerの.ldファイル)によって設定されます。
・MEMORYセクション:使用可能なメモリ領域を定義します。
 各メモリ領域の開始アドレス(ORIGIN)とサイズ(LENGTH)を指定します。
・SECTIONSセクション:セクションの配置を定義します。
 各セクションがどのメモリ領域に配置されるかを指定します。

(参考)
・【 ulimit 】コマンド――ユーザーが使用できるリソースを制限する
https://atmarkit.itmedia.co.jp/ait/articles/1908/01/news031.html

・GNU Cを使いこなそう2017/06/30
リンカスクリプトを理解しよう
https://www.computex.co.jp/article/use_gcc_1.htm