Previous Up Next

Chapter 11  Взаимодействие с языком C

11.1  Введение

Часто, при разработке программного обеспечения на каком–нибудь языке, бывает необходимо интегрировать библиотеки, которые были написанные на других языках программирования по следующим двум причинам:

В данном случае получаем программу, которая собрана из кусков на различных языках и каждый из этих специализированных кусков написан на наиболее подходящем для решения данной проблемы языке. Такие части программы взаимодействуют между собой, передавая данные и выполняя расчет.

В Objective CAML существует механизм взаимодействия с языком C. Из кода Objective CAML можно вызвать функцию на C, передать аргументы и получить результат расчета. Обратное так же возможно: программа на C может вызвать расчет на Objective CAML и затем продолжить обрабатывать полученный результат.

Выбор языка C оправдан следующими причинами:

В последнем случае C выступает в роли эсперанто языков программирования.

Однако, взаимодействие между Objective CAML и C создает определенные трудности, которые представлены ниже:

представление значений в памяти
Например основные значения (int, char, float) в обоих языках различаются представлением в памяти. Необходимо переводить их при обмене данными в обе стороны. То же самое касается структурированных значений: записи, тип сумма или массивы.
сборщик мусора Objective CAML
Реализовать сборщик мусора в C можно, но эта особенность не определена нормой языка. Поэтому, нельзя допустить, чтобы вызов функции на C произвел несовместимые с GC изменения в памяти.
остановка расчета
Механизм исключений в обоих языках различается, в следствии чего возникает проблема перехвата исключений.
разделение общих ресурсов
Буферы Ввода/Вывода, например, не разделяются и не отображают последовательность операций Ввода/Вывода двух программ.

Программы на Objective CAML наследуют надежность статической типизации и автоматическое управление памятью. Корректное использование библиотек на C или взаимодействие посредством C не должно подвергнуть риску эту надежность. Поэтому, для гармоничного взаимодействия обоих языков, необходимо следовать четким правилам.

11.2  План главы

В данной главе, мы рассмотрим средства, входящие в дистрибутив Objective CAML, для взаимодействия с C, которые позволят создать исполняемые файлы, состоящих из частей на обоих языках программирования. Эти средства предоставляют функции, при помощи которых можно перевести значение из одного языка в другой, надежно выделить память в C, используя кучу Objective CAML и GC, а так же возбудить исключения Objective CAML в C.

В первой части главы мы рассмотрим использование C функции в Objective CAML, как создать исполнимый файл и интерактивную среду, которые содержат эту функцию. Во второй части мы изучим каким образом значения Objective CAML представлены в C. В следующей части уточняется как создавать и менять значения Objective CAML в C. В ней так же рассматриваются проблемы, возникающие при выделении памяти в C в “присутствии” GC Objective CAML, а так же способы надежного выделения памяти в C. В пятой главе описано управление исключениями, как они возбуждаются и отлавливаются в зависимости от места остановки расчета. В последней части мы рассмотрим как использовать код Objective CAML в C.


Замечание
Для изучения следующего материала, необходимы знания языка C. Так же, желательно прочесть главу 8, для четкого понимания проблем связанных с автоматической сборкой памяти.

11.3  Передача информации между Objective CAML и C

Передача информации между Objective CAML и C осуществляется созданием исполняемого файла (или интерактивной среды), состоящей из двух частей, которые могут быть скомпилированы раздельно. И тогда, до создания исполнимого файла, компоновщик должен установить связь между именами Objective CAML и C. Для этого программа на Objective CAML должна содержать внешние декларации.

На рисунке 11.1 изображена программа, состоящая из одного куска на C и другого на Objective CAML.


Figure 11.1: Передача информации между Objective CAML и C

Можно представить каждую часть, как содержащую код, инструкции, соответствующие определению функций и вычислению выражений Objective CAML, а так же зоны динамического выделения памяти. Применение функции f к трем целым числам Objective CAML провоцирует вычисление функции f_c на C. Тело функции переводит целые числа Objective CAML в целые числа C, вычисляет сумму и затем возвращает результат переведенный в целое число Objective CAML.

Далее будут представлены первичные элементы взаимодействия Objective CAML и C: внешние декларации, ограничения накладываемые на C функции, которые можно вызвать в Objective CAML и опции редактора связей. Затем мы приведем пример использования ввода/вывода.

11.3.1  Внешние декларации

Внешние декларации функций в Objective CAML необходимы для установления связи между декларацией функции C и именем в Objective CAML, а так же для указания типа функции.

Синтаксис декларации следующий:




Синтаксис 10cmexternal caml_name : type = "C_name"

Эта запись означает, что вызов функции caml_name в Objective CAML спровоцирует вызов функции C C_name с соответствующими аргументами. В примере на изображении 11.1 декларируется функция f, вызов которой соответствует выполнению функции f_c.

Можно объявить функцию, как внешнюю в интерфейсе (т.е. в файле .mli), либо явно указывая на то, что она внешняя, либо как обычное значение:




Синтаксис 10cm external caml_name : type = "C_name" val caml_name : type

Во втором случае, вызов C функции проходит через общий механизм функций Objective CAML. Подобный подход менее эффективен, но он скрывает реализацию функции как C функции.

11.3.2  Декларация C функций

Число аргументов C функций, вызываемых в Objective CAML, должно быть одинаковым с внешним объявлением функции. Тип значений Objective CAML в C: value. Так как представление этих значений является однородным (??), то их можно представить одним единственным типом C. На стр. ?? мы познакомимся со средствами перевода значений и продемонстрируем функцию экспорта значений Objective CAML.

Функция на изображении 11.1 соответствует приведенным ограничениям. Функция f_c связана с функцией Objective CAML, тип которой int -> int -> int -> int и является функцией с тремя аргументами и результатом типа value.

Интерпретатор байт–кода по разному вычисляет вызовы функции в зависимости от числа аргументов1. Если число аргументов меньше или равно пяти, то они помещаются в стек. Если же число аргументов больше пяти, то в C функцию передается вектор, содержащий все входные аргументы и затем число аргументов. Необходимо разделять оба случая, так как C функция может быть вызвана интерпретатором байт–кода. С другой стороны, нативный компилятор всегда вызывает внешнюю функцию передавая ей напрямую все входные аргументы.

Функция с более 5 аргументов

В данном случае нужно написать две функции, одну для байт–код интерпретатора, а другую для нативного. Синтаксис внешних деклараций позволяет использовать одну декларацию для обеих функций C:




Синтаксис 10cm external caml_name : type = "C_name_bytecode" "C_name_native"

У функции C_name_bytecode два аргумента: вектор значений с типом value (C указатель на тип *value) и целое число, которое указывает размер вектора.

Пример

Следующая программа на C определяет две разные функции для сложения шести целых чисел: plus_native для нативного кода и plus_bytecode для вызова из байт–код интерпретатора. Необходимо указать файл mlvalues.h, в котором содержится определение C типов значений Objective CAML и макросы преобразования.

#include <stdio.h> #include <caml/mlvalues.h> value plus_native (value x1,value x2,value x3,value x4,value x5,value x6) { printf("<< NATIVE PLUS >>\n") ; fflush(stdout) ; return Val_long ( Long_val(x1) + Long_val(x2) + Long_val(x3) + Long_val(x4) + Long_val(x5) + Long_val(x6)) ; } value plus_bytecode (value * tab_val, int num_val) { int i; long res; printf("<< BYTECODED PLUS >> : ") ; fflush(stdout) ; for (i=0,res=0;i<num_val;i++) res += Long_val(tab_val[i]) ; return Val_long(res) ; }

Программа на Objective CAML exOCAML.ml вызывает обе C функции.

external plus : int -> int -> int -> int -> int -> int -> int = "plus_bytecode" "plus_native" ;; print_int (plus 1 2 3 4 5 6) ;; print_newline () ;;

Теперь скомпилируем обе программы двумя компиляторами Objective CAML и компилятором C, который назовем cc.

$ cc -c -I/usr/local/lib/ocaml exC.c $ ocamlc -custom exC.o exOCAML.ml -o ex_byte_code.exe $ ex_byte_code.exe << BYTECODED PLUS >> : 21 $ ocamlopt exC.o exOCAML.ml -o ex_native.exe $ ex_native.exe << NATIVE PLUS >> : 21

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

value prim_nat (value x1, ..., value xn) { ... } value prim_bc (value *tab, int n) { return prim_nat(tab[0],tab[1],...,tab[n-1]) ; }

11.3.3  Редактирование связей с C

Из скомпилированных файлов на Objective CAML и C, компоновщик создает исполняемый файл. Результат, полученный нативным компилятором, изображен на рисунке 11.2.


Figure 11.2: Смешанный исполняемый файл

Инструкции программ на Objective CAML и C размещены в статической зоне памяти. В динамической зоне находится стек выполнения (где располагаются текущие вызовы) и кучи Objective CAML и C.

Runtime библиотека

Функции на C, которые могут быть вызваны в программе и использующие лишь стандартные библиотеки, содержатся в execution библиотеке (см. рис. 6.1 на стр. ??). То есть нет необходимости что–либо дополнительно указывать. Однако, в случае если мы используем библиотеки Graphics, Num и Str необходимо указать компоновщику связей библиотеки, соответствующие приведенным модулям. Для этого существует опция компилятора -custom. Тот же принцип применяется и к функциям C, которые мы желаем использовать: необходимо указать объектный файл содержащий эти функции при редактировании связей компоновщиком. Мы продемонстрируем это на примере.

Несколько случаев редактирования связей

Необходимо различать компиляцию нативного компилятора, байт–код компилятор и создание интерактивной среды. Опции для различных типов компиляций описаны в главе 6.

Вернемся к изображению 11.1, чтобы проиллюстрировать три режима компиляции. Для этого переименуем файл Objective CAML в progocaml.ml. В этом файле вызывается внешняя функция из C файла progC.c, который в свою очередь использует C библиотеку libC. После того, как эти файлы скомпилированы независимо друг от друга, редакция связей осуществляется следующими командами:

После этого получим два исполняемых файла: vbc.exe для байт–код версии и для нативной vn.exe.

Создание новой абстрактной машины

Мы можем обогатить runtime библиотеку, включив в нее новые C функции. Для этого существуют следующие команды:

ocamlc -make-runtime -o new_ocamlrun progC.o a_C_library.a

Теперь можно создать байт–код файл vbcnam.exe используя новую абстрактную машину:

ocamlc -o vbcnam.exe -use-runtime new_ocamlrun progocaml.cmo

Данный код можно запустить в виде аргумента новой абстрактной машине командой new_ocaml vbcnam.exe или напрямую vbcnam.exe.


Замечание
Редактирование связей в режиме -custom вынуждает компилятор сканировать объектные файлы .cmo для создания таблицы используемых внешних функций.

Создание интерактивного цикла

Для того чтобы использовать внешнюю функцию в интерактивном цикле, необходимо создать новый цикл, содержащий код C функции и программу на Objective CAML с ее декларацией.

Пусть есть скомпилированный файл progC.c с функцией f_c. Создадим новый интерактивный цикл ftop:

ocamlmktop -custom -o ftop progC.o a_C_library.a ex.ml

В файле ex.ml содержится декларация внешней функции f. Благодаря этому указанная функция известна интерактивному циклу ftop, а код предоставляется объектом progC.o.

11.3.4  Смешивание операций ввода/вывода в C и Objective CAML

Функции C и Objective CAML не разделяют буфера файлового ввода и вывода. Пусть есть программа на C:

#include <stdio.h> #include <caml/mlvalues.h> value hello_world (value v) { printf("Hello World !!"); fflush(stdout); return v; }

Для того, чтобы запись на стандартный выход проходила в правильном порядке, необходимо явно сбрасывать содержимое файловых буферов (fflush).

# external caml_hello_world : unit -> unit = "hello_world" ;; external caml_hello_world : unit -> unit = "hello_world" # print_string "<< " ; caml_hello_world () ; print_string " >>\n" ; flush stdout ;; Hello World !!<< >> - : unit = ()

Полученный вывод не совсем удовлетворительный. Перепишем программу Objective CAML следующим образом:

# print_string "<< " ; flush stdout ; caml_hello_world () ; print_string " >>\n" ; flush stdout ;; << Hello World !! >> - : unit = ()

Систематическое освобождение буфера перед каждой командой записи позволяет соблюсти порядок вывода на экран между двумя языками.

11.4  Анализ значений Objective CAML в C

Машинное представление значений в Objective CAML отличается от C, даже для таких простых типов как целое число. Причиной этому является необходимость в сохранении дополнительной информации GC при сборке памяти. Так как представление значений Objective CAML в памяти однородно, они видны в C как единый тип value.

Каждый раз как в Objective CAML вызывается C функция с аргументами, они должны быть переведены в соответствующий тип. То же самое касается результата C функции, вызываемой из Objective CAML.

Для данной цели существует несколько макросов и функций C. Они находятся в файлах, приведенных в таблице 11.1, которые предоставлены дистрибутивом Objective CAML. Они находятся в папке LIBOCAML/caml, где LIBOCAML является папкой, в которой установлены библиотеки Objective CAML2.


mlvalues.hопределение типа value и макросов перевода типов
alloc.hфункции выделения памяти для значений Objective CAML
memory.hмакросы, более высоко уровня, для перевода типов
Table 11.1: Файлы, используемые для интерфейса с C

11.4.1  Классификация значений value

Значение, тип которого value, может быть:

Куча является зоной памяти, выделяемая программе для динамически размещаемых структур данных. В программе на Objective CAML GC управляет кучей. Программа на C может в свою очередь создать данные в собственной куче и затем передать указатель на выделенное значение Objective CAML.

В таблице 11.2 проведены макросы проверки и перевода типов:


Is_long(v)v целое число Objective CAML?
Is_block(v)v указатель Objective CAML?
Long_val(v)возвращает целое число C long
Int_val(v)возвращает целое число C
Bool_val(v)возвращает "булево" значение C (значение 0 для false)
Table 11.2: Перевод непосредственных значений

Напомним, что в C существует несколько типов для целого числа: short, int и long, тогда как в Objective CAML существует один единственный тип: int.

11.4.2  Доступ к непосредственным значениям

Для представления непосредственных значений Objective CAML используются целые числа:

В следующей C программе определена функция inspect, которая проверяет значение типа value:

#include <stdio.h> #include <caml/mlvalues.h> value inspect (value v) { if (Is_long(v)) printf ("v is an integer (%ld) : %ld", (long) v, Long_val(v)); else if (Is_block(v)) printf ("v is a pointer"); else printf ("v is neither an integer nor a pointer (???)"); printf(" "); fflush(stdout) ; return v ; }

Данная функция проверяет является ли аргумент целым числом. Если да, то сначала на экран выводится значение в виде int C, а затем значение, переведенное макросом Long_val в целое C (long).

Как видно из следующего примера, представление целых числе в Objective CAML отличается от целых C:

# external inspect : 'a -> 'a = "inspect" ;; external inspect : 'a -> 'a = "inspect" # inspect 123 ;; v is an integer (247) : 123 - : int = 123 # inspect max_int;; v is an integer (2147483647) : 1073741823 - : int = 1073741823

Другие стандартные типы, как например char и char представленные непосредственными значениями.

# inspect 'A' ;; v is an integer (131) : 65 - : char = 'A' # inspect true ;; v is an integer (3) : 1 - : bool = true # inspect false ;; v is an integer (1) : 0 - : bool = false # inspect [] ;; v is an integer (1) : 0 - : '_a list = []

Определим тип foo в Objective CAML:

# type foo = C1 | C2 of int | C3 | C4 ;;

Функция inspect выводит различный результат В зависимости от конструктора, функционального или константного,

# inspect C1 ;; v is an integer (1) : 0 - : foo = C1 # inspect C4 ;; v is an integer (5) : 2 - : foo = C4 # inspect (C2 1) ;; v is a pointer - : foo = C2 1

Когда функция распознает непосредственное значение, она выводит "физическое" содержимое этого значения в скобках (т.е. значение в виде целого числа со знаком, занимающего одно машинное слово – int в C), а затем выводит "логическое" содержимое (значение, полученное макросами перевода типов).

В приведенных примерах, мы видим разницу между "физическим" и "логическим" содержимым. Она обусловлена бит–тегом3, который используется GC для того, чтобы различить непосредственное значение от указателя (см. главу 8 на стр. ??).

11.4.3  Дискриминация структурных значений

Кроме непосредственных, все остальные значения являются структурными значениями Objective CAML: n-уплеты, непустые списки, конструкторы с одним и более аргументов, векторы, записи, замыкания и абстрактные значения. Память для них выделяется в куче в виде блоков. У каждого блока имеется заголовок, содержащий информацию о содержимом блока и размере блока в машинных словах. На изображении 11.3 нарисована структура блока для компьютера со словом длинной в 32 бита.


Figure 11.3: Структура блока в куче Objective CAML

Два "цветных" бита используются GC при прохождении графа значений в куче (см. 8, стр ??). Поле "тег" указывает на "тип" значения, которое содержится в блоке. В таблице 11.3 перечисляются функции, возвращающие эту информацию.


Wosize_val(v)возвращает размер блока (без заголовка)
Tag_val(v)возвращает тег блока
Table 11.3: Информация о блоках памяти

В таблице 11.4 указаны различные значения тегов.


from 0 to No_scan_tag-1вектор значений value Objective CAML
Closure_tagзамыкание
String_tagстрока символов
Double_tagвещественное число двойной точности
Double_array_tagвектор вещественных чисел
Abstract_tagабстрактный тип данных
Final_tagабстрактный тип данных с завершающей функцией
Table 11.4: Описание тегов блоков памяти

В зависимости от значения, которое возвращается функцией Tag_val, используются разные макросы для доступа к блоку памяти. Если оно меньше чем No_scan_tag, то структура блока памяти это массив значений value. Макросы, для получения значений из блока памяти описаны в таблице 11.5. В соответствии с языком C и Objective CAML, первый элемент массива находится по индексу 0.


Field(v,n)возвращает n-ое значение вектора
Code_val(v)возвращает code pointer для замыкания
string_length(v)возвращает длину строки
Byte(v,n)возвращает n-ый символ строки, в виде типа char
Byte_u(v,n)то же самое, но возвращает тип unsigned char
String_val(v)возвращает строку с типом C (char *)
Double_val(v)возвращает вещественное
Double_field(v,n)возвращает n-ый элемент вектора вещественных
Table 11.5: Доступ к элементам блока

Как и для непосредственных значений, определим функцию просмотра блока памяти. C функция print_block проверяет входной аргумент (тип value) является ли он непосредственным значением или блоком памяти. Если он оказывается блоком памяти, то функция выведет тип блока и его значение. Данная функция вызывается из функции inspect_block, которая будет вызвана из программы Objective CAML.

#include <stdio.h> #include <caml/mlvalues.h> void margin (int n) { while (n-- > 0) printf("."); return; } void print_block (value v,int m) { int size, i; margin(m); if (Is_long(v)) { printf("immediate value (%d)\n", Long_val(v)); return; }; printf ("memory block: size=%d - ", size=Wosize_val(v)); switch (Tag_val(v)) { case Closure_tag : printf("closure with %d free variables\n", size-1); margin(m+4); printf("code pointer: %p\n",Code_val(v)) ; for (i=1;i<size;i++) print_block(Field(v,i), m+4); break; case String_tag : printf("string: %s (%s)\n", String_val(v),(char *) v); break; case Double_tag: printf("float: %g\n", Double_val(v)); break; case Double_array_tag : printf ("float array: "); for (i=0;i<size/Double_wosize;i++) printf(" %g", Double_field(v,i)); printf("\n"); break; case Abstract_tag : printf("abstract type\n"); break; case Final_tag : printf("abstract finalized type\n"); break; default: if (Tag_val(v)>=No_scan_tag) { printf("unknown tag"); break; }; printf("structured block (tag=%d):\n",Tag_val(v)); for (i=0;i<size;i++) print_block(Field(v,i),m+4); } return ; } value inspect_block (value v) { print_block(v,4); fflush(stdout); return v; }

Различные значения типов блока обрабатываются оператором switch. Определим функцию inspect.

# external inspect : 'a -> 'a = "inspect_block" ;; external inspect : 'a -> 'a = "inspect_block"

Эта функция используется для описания структурных значений Objective CAML. Входной аргумент не должен быть в виде замкнутой (кольцевой) структуры, иначе функция зациклится.

Массивы, n-уплеты и записи

Массивы и n-уплеты представлены в виде массивов value.

# inspect [| 1; 2; 3 |] ;; ....memory block: size=3 - structured block (tag=0): ........immediate value (1) ........immediate value (2) ........immediate value (3) - : int array = [|1; 2; 3|] # inspect ( 10 , true , () ) ;; ....memory block: size=3 - structured block (tag=0): ........immediate value (10) ........immediate value (1) ........immediate value (0) - : int * bool * unit = 10, true, ()

Массивы типов value используются для представления записей Objective CAML в том же порядке что и декларация типов. Тот факт, что поле является изменяемым или нет, не влияет на его физическое представление.

# type foo = { fld1: int ; mutable fld2: int } ;; type foo = { fld1: int; mutable fld2: int } # inspect { fld1=10 ; fld2=20 } ;; ....memory block: size=2 - structured block (tag=0): ........immediate value (10) ........immediate value (20) - : foo = {fld1=10; fld2=20}


Warning
C функции могут беспрепятственно физически изменить неизменяемые значения Objective CAML. Контроль за соответствием использования функций C лежит плечах разработчика.

Тип сумма

Как мы уже видели, константные конструкторы представлены целыми числами. Для представления других конструкторов используется вектор, в котором содержатся аргументы конструктора. Конструктор распознается по значению тега. Этот тег соответствует порядку определения конструктора в типе: 0 соответствует первому конструктору, 1 второму и т.д.

# type foo = C1 of int * int * int | C2 of int | C3 | C4 of int * int ;; type foo = | C1 of int * int * int | C2 of int | C3 | C4 of int * int # inspect (C1 (1,2,3)) ;; ....memory block: size=3 - structured block (tag=0): ........immediate value (1) ........immediate value (2) ........immediate value (3) - : foo = C1 (1, 2, 3) # inspect (C4 (1,2)) ;; ....memory block: size=2 - structured block (tag=2): ........immediate value (1) ........immediate value (2) - : foo = C4 (1, 2)

Тип list это тип сумма, которая определена следующим образом:

type 'a list = [] | :: of 'a * 'a list

У данного типа всего один неконстантный конструктор (::) с единственным тегом 0.

Строка символов

Каждый символ строки занимает один байт. Таким образом блок памяти, представляющий строку, хранит 4 символа в одном машинном слове (на 32 битной архитектуре).


Warning
В строках Objective CAML может содержатся символ с кодом ASCII 0, что соответствует символу конца строки в C.

#include <stdio.h> #include <caml/mlvalues.h> value explore_string (value v) { char *s; int i,size; s = (char *) v; size = Wosize_val(v) * sizeof(value); for (i=0;i<size;i++) { int p = (unsigned int) s[i] ; if ((p>31) && (p<128)) printf("%c",s[i]); else printf("(#%u)",p); } printf("\n"); fflush(stdout); return v; }

Конец строки в Objective CAML определяется с помощью размера блока, из которого она состоит, а также с помощью последнего байта последнего слова блока, в котором указывается число неиспользованных байтов в последнем слове. На следующем примере станет понятней роль последнего байта.

# external explore : string -> string = "explore_string" ;; external explore : string -> string = "explore_string" # ignore(explore ""); ignore(explore "a"); ignore(explore "ab"); ignore(explore "abc"); ignore(explore "abcd"); ignore(explore "abcd\000") ;; (#0)(#0)(#0)(#3) a(#0)(#0)(#2) ab(#0)(#1) abc(#0) abcd(#0)(#0)(#0)(#3) abcd(#0)(#0)(#0)(#2) - : unit = ()

В двух последних примерах (“abcd” et “abcd
000”) длина строк соответственно 4 и 5 символов. По этой причине последний байт меняет свое значение.

Вещественные и вектор вещественных значений

В Objective CAML имеется всего один тип для чисел с плавающей запятой: float. Значения для типа float выделяются в куче и имеют размер в 2 машинных слова.

# inspect 1.5 ;; ....memory block: size=2 - float: 1.5 - : float = 1.5 # inspect 0.0;; ....memory block: size=2 - float: 0 - : float = 0

Вектор вещественных значений имеет специальную структуру, которая позволяет оптимально использовать память. По этой причине у такого вектора имеется специальный, отличный от других тегов макрос доступа.

# inspect [| 1.5 ; 2.5 ; 3.5 |] ;; ....memory block: size=6 - float array: 1.5 2.5 3.5 - : float array = [|1.5; 2.5; 3.5|]

Благодаря данной оптимизации Objective CAML может выполнять числовые расчеты с подобным типом данных намного быстрее, чем при использовании указателя на кучу.


Warning
Когда в C необходимо выделить вектор для вещественных чисел Objective CAML, размер вектора вычисляется по следующей формуле: число элементов помноженное на Double_wosize. Данный макрос указывает число слов необходимых для вещественных чисел с двойной точностью.
За исключением float array, вещественные содержащиеся в других структурах данных сохраняют их обычную представление, то есть как структурное значение, выделенное в куче. В следующем примере анализируется список вещественных.

# inspect [ 3.14; 1.2; 7.6];; ....memory block: size=2 - structured block (tag=0): ........memory block: size=2 - float: 3.14 ........memory block: size=2 - structured block (tag=0): ............memory block: size=2 - float: 1.2 ............memory block: size=2 - structured block (tag=0): ................memory block: size=2 - float: 7.6 ................immediate value (0) - : float list = [3.14; 1.2; 7.6]

Список виден как блок размером в два слова, в которых содержится заголовок и хвост списка. Заголовок списка это вещественное, также размером в два слова.

Замыкание

Функциональное значение характеризуется кодом с одной стороны и окружением с другой (см. 1 на стр. ??). Существует два способа, с помощью которых можно получить функциональное значение: явно использовать абстракцию (как например в fun x -> x+1) или частично применить функцию ((fun x -> fun y -> x+y) 1).

В окружении замыкания может находится 3 типа переменных: глобальные, локальные и унаследованные частичным применением. Реализация все трех категорий переменных различна. Глобальные переменные являются частью глобального окружения и явно не видны в окружении замыкания. Как мы увидим далее, локальные и унаследованные частичным применением параметры могут находится в замыкании. Таким образом, с точки зрения реализации, окружение замыкания касается лишь локальных и абстрактных параметров.

Если окружение замыкания пусто, то в нем имеется лишь указатель на код.

# let f = fun x y z -> x+y+z ;; val f : int -> int -> int -> int = <fun> # inspect f ;; ....memory block: size=1 - closure with 0 free variables ........code pointer: 0x807308c - : int -> int -> int -> int = <fun>

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

# let a1 = f 1 ;; val a1 : int -> int -> int = <fun> # inspect (a1) ;; ....memory block: size=3 - closure with 2 free variables ........code pointer: 0x8073088 ........memory block: size=1 - closure with 0 free variables ............code pointer: 0x807308c ........immediate value (1) - : int -> int -> int = <fun> # let a2 = a1 2 ;; val a2 : int -> int = <fun> # inspect (a2) ;; ....memory block: size=4 - closure with 3 free variables ........code pointer: 0x8073088 ........memory block: size=1 - closure with 0 free variables ............code pointer: 0x807308c ........immediate value (1) ........immediate value (2) - : int -> int = <fun>

На рис. 11.4 графически изображен предыдущий вывод.


Figure 11.4: Представление замыкания

В функции f отсутствуют свободные переменные. Для замыкания без окружения, указатель кода указывает на код, который необходимо применить, когда все необходимые аргументы будут указаны, то есть в данном случае это x+y+z. Замыкание с окружением указывает на один и тот же общий код (здесь один и тот же код для a1 и a2). Данный код проверяет что все необходимые аргументы на месте и в этом случае они проталкиваются и код выполняется. В противном случае будет создано новое замыкание, с окружением в котором как всегда на первом месте указатель на замыкание без окружения из которого частичное замыкание получено. Это позволяет запустить реальный код с одной стороны и хранить указатель на самого себя для рекурсивных функций.

В случае когда используется локальная декларация, частичное применение выглядит так:

# let g x = let y=2 in fun z -> x+y+z ;; val g : int -> int -> int = <fun> # let a1 = g 1 ;; val a1 : int -> int = <fun> # inspect a1 ;; ....memory block: size=3 - closure with 2 free variables ........code pointer: 0x8086548 ........immediate value (1) ........immediate value (2) - : int -> int = <fun>

Вызов замыкания Objective CAML из C рассмотрен в гл. 11.4.4.

Абстрактные типы

Значение абстрактного типа представлено вектором значений value. На самом деле, информация о типе нужна лишь синтезатору типов. Во время выполнения программы информация о типе не требуется, лишь только GC необходимо знать представление в памяти и размер значений.

11.4.4  Вызов замыкания Objective CAML в C


1
Напомним, что в функции fst с типом 'a * 'b -> 'a имеет один аргумент — пара.
2
По умолчанию в Unix это /usr/local/lib/ocaml. В Windows C:
OCAML
LIB
3
В приведенных примерах бит–тег является младшим битом.

Previous Up Next