Я працюю з компілятором для мікросхеми DSP, який свідомо генерує код, який отримує доступ один минулий кінець масиву з коду C, який не робить!
Це відбувається тому, що петлі побудовані так, що кінець ітерації попередньо вибирає деякі дані для наступної ітерації. Таким чином, попередньо встановлена дата в кінці останньої ітерації ніколи фактично не використовується.
Написання подібного коду С посилається на невизначене поведінку, але це лише формальність із стандартного документа, що стосується максимальної переносимості.
Частіше, ні, програма, яка отримує доступ за межі, не є розумно оптимізованою. Це просто баггі. Код отримує деяке значення сміття, і, на відміну від оптимізованих циклів вищезгаданого компілятора, код потім використовує значення в наступних обчисленнях, тим самим пошкоджуючи їх.
Варто ловити помилки таким чином, і тому варто зробити поведінку невизначеною навіть тільки з цієї причини: щоб час запуску міг створити діагностичне повідомлення типу "перевищення масиву в рядку 42 main.c".
У системах з віртуальною пам’яттю може виникнути розподіл масиву таким чином, що наступна адреса знаходиться у незробленій області віртуальної пам’яті. Після цього доступ буде бомбити програму.
Зауважте, що в C нам дозволено створити вказівник, який знаходиться в кінці масиву. І цей покажчик повинен порівняти більше, ніж будь-який вказівник, на внутрішній масив. Це означає, що реалізація C не може розмістити масив прямо в кінці пам'яті, де адреса один плюс обернеться і буде виглядати менше, ніж інші адреси в масиві.
Тим не менш, доступ до неініціалізованих значень або за межами меж іноді є дійсною методикою оптимізації, навіть якщо не є максимально портативною. Це, наприклад, чому інструмент Valgrind не повідомляє про доступ до неініціалізованих даних, коли ці звернення трапляються, але лише тоді, коли значення пізніше використовується якимось чином, що може вплинути на результат програми. Ви отримуєте діагностику на кшталт "умовна гілка в xxx: nnn залежить від неініціалізованого значення", і іноді важко відстежити, звідки вона походить. Якби всі подібні звернення були негайно захоплені, було б багато помилкових позитивних результатів, що випливають з коду, оптимізованого компілятором, а також правильно оптимізованого кодом.
Якщо говорити про це, я працював з деяким кодеком від постачальника, який видав ці помилки під час перенесення в Linux та запуску під Valgrind. Але продавець переконав мене, що лише кілька бітвикористовуваного значення насправді походить з неініціалізованої пам'яті, і ці біти ретельно уникали логіки. Використовувалися лише хороші біти значення, і Valgrind не має можливості відстежувати окремий біт. Неініціалізований матеріал з’явився з прочитанням слова в кінці бітового потоку кодованих даних, але код знає, скільки бітів у потоці і не використовуватиме більше бітів, ніж насправді є. Оскільки доступ за межі кінця масиву бітових потоків не завдає ніякої шкоди архітектурі DSP (немає ні віртуальної пам’яті після масиву, ні портів, відображених у пам’яті, а адреса не обертається), це правильна техніка оптимізації.
"Не визначена поведінка" насправді не означає багато, тому що згідно з ISO C, просто включення заголовка, який не визначений у стандарті C, або виклик функції, яка не визначена в самій програмі або стандарті C, є прикладами невизначених поведінка. Невизначена поведінка не означає "не визначена ніким на планеті", просто "не визначена стандартом ISO C". Але, звичайно ж , іноді невизначений поведінка дійсно є абсолютно не визначений ніким.