cgo
Структуры в Си и CGO
По горячим следам хочу написать какая забытая мною особенность языка Си вчера ввела меня в заблуждение и почему несмотря на то, что я работал с сишной структурой, CGO так как будто не считало.
Для начала посмотрим как определяются и передаются структуры в Си. Полную спецификацию я приводить не буду, посмотрим на два распространённых способа. Первый используется в этом небольшом примере:
#include <stdio.h>
#include <math.h>
struct point {
double x, y;
};
double point(struct point p) {
return sqrt(pow(p.x, 2) + pow(p.y, 2));
}
int main() {
printf("%g\n", point((struct point){1, 2}));
}Тут можно обратить внимание на две особенности —
Это происходит потому, что имена структур обитают в своём собственном пространстве имён, на его использование и указывает struct. Там же живут имена объединений (union) и перечислений (enum). Имена функций находятся в общем пространстве, так что два таких определения с одним именем друг другу не мешают.
Структуры, объявленные таким образом видны через CGO компилятора Гоу как C.struct_name (C.struct_point в данном случае). К сожалению, тут есть особенность, о которую я споткнулся — если запрашиваемая структура не определена, то Гоу не выдаст ошибки, мы просто получим пустую структуру:
package main
import ("C"; . "fmt")
func main() {
Println(C.struct_undefined{}) // ошибки не будет
}Но программисты — люди ленивые, писать всюду struct неудобно. К счастью есть способ этого не делать — можно завести структуру, а потом дать ей имя в пространстве всех остальных типов. Это делается при помощи ключевого слова typedef позволяющего создавать алиасы типов:
struct point {
double x, y;
};
typedef struct point point;Теперь у нас есть два имени для одного типа — один в пространстве структур и прочего, второе — в общем пространстве. Можно даже сделать их одинаковыми, что очень удобно. Если второе имя нам ни к чему (бывают случаи, когда оно необходимо, но я не буду их тут рассматривать), то можно определить анонимную структуру и сразу дать имя этому типу в общем пространстве, так тоже будет работать.
Это нас подводит ко второму способу работы со структурами:
#include <stdio.h>
#include <math.h>
typedef struct {
double x, y;
} point;
double veclen(point p) {
return sqrt(pow(p.x, 2) + pow(p.y, 2));
}
int main() {
printf("%g\n", veclen((point){1, 2}));
}Ключевое слово struct перед типом уже не нужно, раз все имена в общем пространстве. Это же касается и CGO.
Как раз на эти грабли я и наступил — позабыл про эти все особенности. Когда я ещё писал на Си, всегда пользовался вторым способом, с именем в общем пространстве и привык к тому, что это и есть структуры.
Структуры и CGO
Обновление: в комментариях в Фейсбуке мне бывший коллега попенял, что я забыл Си и что поведение правильное — тут объявляется анонимная структура, а потом к ней определяется тип. Верно, практики на Си у меня за последние годы почти не было, вымылось всё из памяти. Так что текст ниже читайте с учётом этого обновления заметки.
Каждый раз расстраиваюсь, когда сталкиваюсь с CGO.
Документация говорит нам, что сишные структуры доступны в Гоу через конструкцию C.struct_name, а её размер — через C.sizeof_struct_name.
Я так и пытался сделать и получал ошибки:
package main
/*
typedef struct {
int x, y;
} test;
*/
import "C"
import . "fmt"
func main() {
Println(C.struct_test{}) // «{}»? Что за…
Println(C.sizeof_struct_test) // could not determine kind of name for C.sizeof_struct_test
}Упрощённый код выше кратко иллюстрирует проблему. В первом случае никакая ошибка не выводится (что ещё больше сбивает с толку), но структура
Оказывается, и я набрёл на это случайно, в ходе экспериментов, struct просто не надо писать, тогда всё будет работать:
Println(C.test{}) // «{0 0}»
Println(C.sizeof_test) // 8Если будет время, ещё поизучаю этот вопрос — посмотрю историю коммитов и всё такое, возможно была