Мы знаем, что улкат() получает указатель на целевой массив в качестве параметров и объединяет их с исходной строкой. Целевой массив должен быть достаточно большим, чтобы сохранить конкатенированный результат. Недавно я обнаружил, что функция strcat() по-прежнему может выполняться, как и ожидалось, для небольших программ, даже если целевой массив недостаточно велик для добавления второй строки. Я начал просматривать stackoverflow и обнаружил пара - ответы для этого вопроса. Я хочу углубиться и понять, что именно происходит на аппаратном уровне, когда я запускаю этот код ниже?
#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstring>
using namespace std;
int main(){
char p[6] = "Hello";
cout << "Length of p before = " << strlen(p) << endl;
cout << "Size of p before = " << sizeof(p) << endl;
char as[8] = "_World!";
cout << "Length of as before = " << strlen(as) << endl;
cout << "Size of as before = " << sizeof(as) << endl;
cout << strcat(p,as) << endl;
cout << "After concatenation:" << endl;
cout << "Length of p after = " << strlen(p) << endl;
cout << "Size of p after = " << sizeof(p) << endl;
cout << "Length of as after = " << strlen(as) << endl;
cout << "Size of as after = " << sizeof(as) << endl;
return 0;
}
После запуска этого кода длина массива p[] равна 12, а размер p[] равен 6. Как физически такая длина может храниться в массиве такого размера? Я имею в виду, что для этого массива количество байтов ограничено, значит ли это, что функция strlen(p) ищет только терминатор NULL и продолжает считать, пока не найдет его, и игнорирует фактический выделенный размер этого массива. И функцию sizeof() на самом деле не волнует, хранит ли последний элемент в массиве, специально выделенный для нулевого символа, нулевой символ или нет.
Возможный дубликат Преднамеренное переполнение буфера, которое не всегда приводит к сбою программы.





Массив p размещается во фрейме стека функций, поэтому strcat "переполняет" буфер p и продолжает запись в какую-то другую область стека - обычно он переопределяет другие локальные параметры, адрес возврата функции и т. д. (имейте в виду, что на платформе x86 стек функций обычно растет «вниз», т.е. в сторону меньших адресов). Это хорошо известная уязвимость «переполнения буфера».
strlen не может знать реальный размер вашего буфера, он просто ищет 0-терминатор. С другой стороны, sizeof — это функция времени компиляции, которая возвращает размер массива в байтах.
Почти уверен, что стек вызовов был предназначен. Но что, если реализация спрашивающего на C++ не использует стек?
@SidS, вероятно, имел в виду область стека
@Sid S, ты прав, формулировка неточна, лучше было бы «фрейм стека функций».
Вы пишете за пределами p, и поэтому поведение вашей программы не определено.
Хотя поведение полностью не определено, есть несколько распространенных вариантов поведения:
Вы перезаписываете некоторые несвязанные данные. Это могут быть другие локальные переменные, адрес возврата функции и т. д. Невозможно точно угадать, что будет перезаписано, не изучив сборку, сгенерированную компилятором для этой конкретной программы. Это может привести к серьезной уязвимости системы безопасности, поскольку позволяет злоумышленнику внедрить свой собственный код в пространство памяти вашей программы и перезаписать адрес возврата функции, чтобы заставить программу выполнить внедренный код.
Программа вылетает. Это может произойти, если вы пишете достаточно далеко за конец массива, чтобы пройти границу страницы памяти. Программа может попытаться выполнить запись в адрес виртуальной памяти, который ОС не сопоставила с физической памятью для вашего приложения. Это приводит к тому, что ОС убивает ваше приложение (например, с помощью SIGSEGV в Linux). Обычно это происходит чаще с динамически размещаемыми массивами, чем с локальными массивами функций.
Может переполнение кучи? Со временем, когда вы начнете писать рекурсивные функции, вы можете достичь переполнения стека.