Взаимодействие с C и C++
Контекст: Linux x86-64, NASM + GCC. Соглашение System V AMD64 ABI. Windows использует другой порядок регистров — см. подпрограммы.
Общая схема
- Ассемблерная функция с именем, видимым линкеру (
global). - C-файл с
externобъявлением с тем же именем (без подчёркивания в Linux для C; C++ требуетextern "C"). - Сборка:
nasm -f elf64→.o, затемgccлинкует сmain.c.
Имя в объектнике должно совпадать: в NASM для C-линковки часто пишут global my_add и в C — long my_add(long, long);.
Ассемблер вызывается из C
add.asm:
section .text
global my_add
my_add:
mov rax, rdi ; 1-й аргумент (long)
add rax, rsi ; 2-й аргумент
ret
main.c:
#include <stdio.h>
long my_add(long a, long b);
int main(void) {
printf("%ld\n", my_add(10, 32));
return 0;
}
Сборка:
nasm -f elf64 add.asm -o add.o
gcc main.c add.o -o demo
my_add возвращает значение в RAX (целые до 64 бит). Вещественные — в XMM0 и далее по ABI.
C вызывается из ассемблера
main.asm:
section .text
global main
extern printf
main:
push rbp
mov rbp, rsp
sub rsp, 16 ; выравнивание стека на 16 байт перед call в ABI
lea rdi, [rel fmt] ; 1-й аргумент C
mov rsi, 42 ; 2-й
xor rax, rax ; для variadic: число XMM-регистров = 0
call printf
xor eax, eax
leave
ret
section .rodata
fmt db 'value=%ld', 10, 0
Сборка: nasm -f elf64 main.asm -o main.o && gcc main.o -o demo -no-pie (или точка входа main через стандартный crt).
Важно для вызовов libc:
- Перед
callк функциям вродеprintfстекRSPдолжен быть кратен 16 (послеcallвнутри libc снова выровняют; типичный прологpush rbp; mov rbp,rsp; sub rsp,Nс нечётным числомpushломает выравнивание). - Для variadic (
printf,scanf) вALпередают число использованных XMM-аргументов.
Кто сохраняет регистры
По System V AMD64 (кратко):
| Сохраняет вызывающий (caller-saved) | Сохраняет вызываемый (callee-saved) |
|---|---|
| RAX, RCX, RDX, RSI, RDI, R8–R11, XMM0–XMM15 | RBX, RBP, R12–R15 |
Если ассемблерная функция портит RBX, она обязана восстановить его до ret. Если C вызывает ваш код и вы меняете R12–R15 — то же самое.
Подробнее — Команды и подпрограммы.
C++ и extern "C"
Имена в C++ декорируются (_Z3fooil). Ассемблерный модуль для линковки с C++:
extern "C" int asm_helper(int x);
и в NASM: global asm_helper.
Точка входа — _start и main
| Точка | Кто инициализирует |
|---|---|
_start | никто — сами syscall, без libc |
main | код запуска из crt (libc), можно printf, malloc |
Смешивать в одном проекте два global main или _start нельзя.
Практический минимум для горячего пути
Типичный паттерн оптимизации: ядро на ассемблере, обвязка на C.
// crypto_wrapper.c
extern void block_transform(const unsigned char *in, unsigned char *out);
void process_buffer(const unsigned char *buf, size_t len) {
unsigned char tmp[16];
for (size_t i = 0; i < len; i += 16)
block_transform(buf + i, tmp);
}
Ассемблер реализует block_transform с фиксированным ABI; C не знает про регистры внутри.
Ошибки при стыковке
| Симптом | Вероятная причина |
|---|---|
Segfault в printf | стек не выровнен на 16 |
| Неверный результат | аргументы не в rdi/rsi/... |
undefined reference | нет global в .asm или другое имя |
| Работает в C, падает из asm | испорчен callee-saved регистр |
Связанные материалы
- Разбиение на файлы — линковка модулей.
- Системные вызовы без libc — архитектура программ и справочник.
См. также
Другие статьи этого же раздела в боковом меню (как на странице «О разделе»). Полный отказ от высокоуровневых языков нецелесообразен. Поэтому большинство компиляторов поддерживают встроенный ассемблер — механизм вставки ассемблерных инструкций непосредственно в код на C/C++. %macro, %define и %if в NASM — шаблоны инструкций без дублирования исходного текста. Разделение программы на .asm-файлы, global и extern, сборка объектников и линковка в ELF. Секции ELF, символы, objdump и сопоставление дизассемблирования с исходным NASM-кодом. REP, MOVS, SCAS, STOS, флаг DF и доступ к данным по индексу через таблицу. SSE2 для float и double, регистры XMM, выравнивание; кратко про стек x87 и AVX. Microsoft x64 calling convention, shadow space, вывод в консоль и файлы через API вместо syscall. Основы ассемблера - синтаксис Intel/AT&T, базовые инструкции и принципы низкоуровневого программирования. Архитектура ассемблерных программ - взаимодействие с ОС, вызовы библиотек и организация низкоуровневого кода. Типизация, набор правил определения типа данных значений языка. Управляющие конструкции и команды процессора в ассемблере - регистр команд, переходы и управление потоком исполнения. Команды и подпрограммы в ассемблере - передача параметров, соглашения вызовов и работа со стеком.История ассемблерных языков
Макросы и условная сборка
Несколько модулей и линковка
Чтение исполняемого файла и листинга
Строковые инструкции и таблицы поиска
Числа с плавающей точкой и SIMD
Windows x64, WinAPI и отличия от Linux
Основы ассемблера
Архитектура ассемблерных программ
Типы данных и регистры
Управляющие конструкции и команды процессора
Команды и подпрограммы