2016-12-31 16 views
8

Если вы хотите получить фон, см. here. Короче говоря, возникает вопрос: «Какая фактическая разница между bracket (mallocBytes n) free и allocaBytes от Foreign.Marshall.Alloc».В чем разница между «скобкой (mallocBytes n) free» и «allocaBytes»?

Обычно в C, alloca выделяет стек, а malloc выделяет кучу. Я не уверен, что с ним происходит в Haskell, но я не ожидал бы разницы между вышеупомянутыми уравнениями, кроме скорости. Если вы нажмете ссылку на фоновый рисунок, вы знаете, что с компилированным кодом bracket (mallocBytes n) free в результате получилось «двойное свободное или повреждение», а allocaBytes отлично работает (проблема не видна, когда в GHCi вообще все отлично работает в обоих случаях).

К настоящему времени я провел два дня в болезненной отладке, и я уверен, что bracket (mallocBytes n) free как-то нестабилен, а остальная часть кода надежна. Я хотел бы узнать, в чем заключена сделка с bracket (mallocBytes n) free.

+1

Возможно ли, что буфер переполнен в * обеих * ситуациях, и это просто, что 'mallocBytes' и' free' ловят ошибку, а другая приводит к молчанию коррупции? Похоже, что 'allocaBytes' в конечном счете использует примитив GHC' newPinnedByteArray # ', поэтому он не кажется простым вызовом' malloc'. –

+0

Я не думаю, что буфер переполнен вообще. Теперь у меня есть тесты, которые подтверждают с помощью кодирования/декодирования и сравнения блоков данных WAVE, что привязки кодера и декодера работают правильно, а декодированные данные обратно соответствуют оригиналу. Если буфер переполнен, это, скорее всего, приведет к повреждению данных. – Mark

+0

Не обязательно. «Неопределенное поведение», ну, не определено. Это может сработать, это также может привести к тому, что ваша кошка начнет гореть. Обратите внимание, что двойное использование 'free' обычно показывает повреждение памяти, а не обязательно второе использование' free' в этой ячейке памяти. – Zeta

ответ

12

bracket (mallocBytes size) free будет использовать память C о malloc и free, тогда как allocaBytes size будет использовать, но под управлением сборки мусора GHCs. Это само по себе огромная разница уже, так как Ptr из allocaBytes может быть окружен неиспользуемой (но выделенной) памяти:

import Control.Exception 
import Control.Monad (forM_) 
import Foreign.Marshal.Alloc 
import Foreign.Ptr 
import Foreign.Storable 

-- Write a value at an invalid pointer location 
hammer :: Ptr Int -> IO() 
hammer ptr = pokeElemOff ptr (-1) 0 >> putStrLn "hammered" 

main :: IO() 
main = do 
    putStrLn "Hammer time! Alloca!" 
    forM_ [1..10] $ \n -> 
    print n >> allocaBytes 10 hammer 

    putStrLn "Hammer time! Bracket" 
    forM_ [1..10] $ \n -> 
    print n >> bracket (mallocBytes 10) free hammer 

Результат:

Hammer time! Alloca! 
1 
hammered 
2 
hammered 
3 
hammered 
4 
hammered 
5 
hammered 
6 
hammered 
7 
hammered 
8 
hammered 
9 
hammered 
10 
hammered 
Hammer time! Bracket 
1 
hammered 
<program crashes> 

Как вы можете видеть, хотя мы arr[-1] = 0, allocaBytes счастливо проигнорировал эту ошибку. Тем не менее, free будет (часто) взорваться на вашем лице, если вы напишете в позицию -1. Он также взорвется на вашем лице, если в другой выделенной области памяти будет повреждение памяти *.

Кроме того, с allocaBytes, вероятно, указатель указывает где-то в уже выделенную память, а не на начало одного, например.

nursery = malloc(NURSERY_SIZE); 

// ... 

pointer_for_user = nursery + 180; 

// pointer_for_user[-1] = 0 is not as 
// much as a problem, since it doesn't yield undefined behaviour 

Что это значит? Ну, allocaBytes с меньшей вероятностью взорвется в вашем лице, но за счет того, что вы не заметите, приведет ли ваш код C-кода к повреждению памяти. Хуже того, как только вы напишете за пределами границ, возвращаемых allocaBytes, ваш возможный развращающий другой Haskell значения молча.

Однако мы говорим здесь о неопределенном поведении. Приведенный выше код может или не может быть поврежден в вашей системе. Он может также произойти сбой в части allocaBytes.

Если бы я был вами, я бы trace the malloc and free calls.


* Однажды у меня был «двойное использование свободного» ошибка в середине моей программы. Отлаживал все, переписал большую часть «плохой» рутины. К сожалению, ошибка исчезла в отладочных сборках, но повторяется в выпусках. Оказалось, что в первых десяти строках main я случайно написал b[i - 1] с i = 0.

+0

Хороший вопрос об отслеживании звонков, я попробую. – Mark

+1

Вы были правы, я смешал размеры и размеры блоков, а так как размер блока часто больше, чем размер кадра, на границе буфера было написано довольно много байтов. – Mark

5

Я смог повторить проблему, и я могу подтвердить, что происходит существенное переполнение буфера.Если вы используете следующий распределитель (пожалуйста, извините быстрый и грязный код), который добавляет стоимость страницы 0xa5 s после буфера и выгружает ее, если она изменена, вы можете увидеть превышение нескольких сотен байтов в нескольких тестах:

withBuffer :: Int -> (Ptr a -> IO b) -> IO b 
withBuffer n = bracket begin end 
    where begin = do 
      a <- mallocBytes (n + 4096) 
      mapM_ (\i -> pokeByteOff (a `plusPtr` n) i (0xa5 :: Word8)) [0..4095] 
      return a 
     end = \a -> do 
      page <- mapM (\i -> peekByteOff (a `plusPtr` n) i) [0..4095] 
      when (any (/= (0xa5 :: Word8)) page) $ do 
      putStrLn $ unlines $ map (hexline page) [0,16..4095] 
      error "corruption detected" 
      free a 
     hexline bytes off = unwords . map hex . take 16 . drop off $ bytes 
     hex = printf "%02x" 
+0

Спасибо за вашу помощь, я думаю, я знаю, где искать сейчас ;-) – Mark

+0

Ах, старая канарейка. Кстати, я бы изменил формулировку, так как ваш код не «добавляет страницу» (дополнительная память может не начинаться с выравнивания страницы). Кроме того, вы можете упростить 'hexline' до' hexline bytes off = unwords. map hex. возьмите 16. падение байтов $. – Zeta

+0

Спасибо, все сделано. –