Мне сложно точно описать проблему. Позвольте мне показать код ниже:
У меня есть функция в C:
int foo(char *deviceID){
char a[] = "abc";
deviceID=a;
return 1;
}
Очевидно, я передаю аргументы char*deviceID для изменения в этой функции, просто игнорирую возвращаемое целое число, я хочу получить значение deviceID;
В Го:
func getID() string{
var b []byte
C.foo((*C.char)(unsafe.Pointer(&b)))
return string(b)
}
Но, кажется, я ничего не получил. Может ли кто-нибудь помочь мне выяснить проблему?
@GSerg, так что ключевая проблема здесь в неправильном коде C? Не могли бы вы помочь мне настроить его, чтобы удовлетворить мою потребность?
Совершенно непонятно, что вы пытаетесь сделать. Возможно, вы хотите strcpy(deviceID, "abc")
, но если вы не передадите размер доступного хранилища в качестве параметра функции, сделать это безопасно будет невозможно.
@WilliamPursell Но что, если бы я уже знал максимальный размер идентификатора устройства? Могу ли я назначить этот конкретный размер [] байт для кода C? Это должно быть правильным способом, не так ли?
char *deviceID => Это указатель, который указывает на первый символ массива вызова (код клиента). Можно сказать, что deviceID хранит адрес начального адреса массива вызывающего (клиентский код). Предположим, что это массив B. Таким образом, он хранит адрес B[0]
deviceID=a => deviceID хранит начальный адрес массива [a].
Когда программа возвращается, deviceID указывает на первый символ. Можно сказать, что deviceID снова хранит адрес начального адреса B[0].
Нет никаких изменений.
В C мы можем использовать strcpy для копирования содержимого по адресу.
int foo(char *deviceID){
char a[] = "abc";
strcpy(deviceID, a);
return 1;
}
ИЛИ
int foo(char *deviceID){
char a[] = "abc";
for (int i = 0; i < 4; i++){
a[i] = a[i]; //note: copy the last character such as a[3] = '\0';
}
return 1;
}
Приведенный выше код C ничего не дает. Вы проверили результат?
@AllenZHU Да, я проверил. Предварительное условие: в идентификаторе устройства достаточно места для хранения результата, например: char buffer[4]; int ret = foo (буфер);
Итак, я думаю, вы также должны показать код go, что вы изменили.
Как отметил GSerg в комментарии и показано в ответе Тхань До, ваш код C неверен (обычно компилируется и запускается, но не имеет полезного эффекта).
Само по себе написание правильного кода на C может быть достаточно сложным; подключить его к среде выполнения Go может быть еще сложнее. Ваш существующий код C имеет одно возвращаемое значение, но вы, вероятно, хотели бы, чтобы он возвращал два значения: целое число и указатель на символ, указывающий на первое из последовательности значений char
, заканчивающихся байтом '\0'
, т. е. , строка C. Однако строки в C известны своей сложностью.
Ваша локальная переменная a
содержит подходящую строку:
char a[] = "abc";
Здесь a
имеет тип array 4 of char
или char[4]
(в C, то есть в Go наиболее близким совпадением будет [4]byte
), но время жизни этой переменной — только до конца самой функции. В частности, массив a
имеет блочную область действия и автоматическую продолжительность. 1 Это означает, что четыре байта, содержащие 'a', 'b', 'c', '\0'
, исчезают , как только возвращается сама функция.
Есть много разных способов решить эту проблему в вашем коде C. Какой из них подходит, зависит от реальной проблемы: в этом игрушечном примере, в котором вы воспроизвели проблему, самым простым способом было бы дать vraiable a
статическую продолжительность. Результат будет выглядеть следующим образом:
int foo(char **deviceID) {
static char a[] = "abc";
*deviceID = a;
return 1;
}
Обратите внимание, что эта функция теперь принимает указатель на указатель. Из большего кода C это может быть вызвано так:
char *name;
ret ok;
ret = foo(&name);
Указатель вызывающего объекта name
типа char *
был заполнен — перезаписан — соответствующим значением типа char *
, т. продолжительность").
Другой метод, показанный в ответе Тхань До, заключается в использовании strcpy
. Делать это опасно, потому что теперь вызывающая сторона должна выделить достаточно памяти для хранения всей строки. Теперь необходимо выбрать максимальную длину для заполнения foo
(или добавить параметр, чтобы уменьшить опасность, что мы и сделаем через мгновение). Наш вызывающий фрагмент кода C теперь может выглядеть так:
char buf[4];
ret ok;
ret = foo(buf);
но вы должны спросить: откуда мы знаем, что в buf
есть место для четырех байтов? Ответ: либо «мы этого не делаем» — нам просто повезло сделать его достаточно большим, — либо «потому что мы видим, что функция foo
всегда записывает ровно четыре байта». Но если мы воспользуемся вторым ответом, мы увидим, что функция foo
всегда записывает четыре байта 'a', 'b', 'c', '\0'
(а затем всегда возвращает 1). Так зачем же мы вообще вызывали функцию foo
? Мы могли бы просто написать:
char buf[4] = "foo";
int ret = 1;
и полностью исключить вызов функции. Таким образом, реальный пример, возможно, будет принимать два аргумента: указатель на буфер, который нужно заполнить, и размер этого буфера в байтах. Если нам нужно больше места, чем доступно, в нашей функции foo
мы бы вернули ошибку, на которую вызывающая сторона должна теперь обратить внимание, или усекли бы имя, или, возможно, и то, и другое:
int foo(char *buffer, size_t len) {
char a[] = "abc";
if (strlen(a) + 1 > len) {
return 0; /* failure */
}
strcpy(buffer, a);
return 1; /* success */
}
Функция foo
теперь зависит от вызывающей стороны для правильной отправки обоих значений, но, по крайней мере, ее можно использовать правильно, но в целом безопасно — в отличие от более ранней версии foo
.
1Эти термины специфичны для языка C. Хотя объявления Go также имеют область действия, этот термин используется немного по-другому, поскольку базовое хранилище в Go является сборщиком мусора. Продолжительность некоторых данных в C может быть привязана к области действия переменной, чего просто не происходит в Go.
Независимо от того, что вы делаете со своим кодом C, ваш существующий код Go также имеет проблему:
func getID() string {
var b []byte
C.foo((*C.char)(unsafe.Pointer(&b)))
return string(b)
}
Тип b
здесь []byte
или «кусок байта». Он имеет начальное значение, равное нулю (преобразованное в тип среза байта).
Значение среза в Go на самом деле представляет собой группу из трех элементов, своего рода тип struct
, если хотите. См. Reflect.SliceHeader для получения дополнительной информации. Конструкция &b
создает указатель на заголовок этого фрагмента.
В зависимости от того, как вы исправляете свою функцию foo
— использует ли она strcpy
, или устанавливает значение типа char *
, принимая значение типа char **
, или даже использует malloc
, как в ответе Allen ZHU — вы определенно не хотите проходить &b
в код Си. Оборачивание &b
в unsafe.Pointer
просто маскирует эту ошибку.
Вместо этого скажем, что вы решили C.foo
взять char *
и size_t
. Затем мы хотим передать функцию C:
*C.char
, указывающее на первое из n >= 4 C.char
s;C.char
, которое может быть перезаписано, включая завершающий `\0'.Мы должны сохранить возвращаемое значение и убедиться, что это магическая константа 1 (было бы неплохо избавиться и от магических констант, но вы можете сделать это позже).
Вот наша модифицированная функция getID
:
func getID() string {
b := make([]C.char, 4)
ret := C.foo(&b[0], C.size_t(len(b)))
if ret != 1 {
panic(fmt.Sprintf("C.foo failed (ret=%d)", int(ret)))
}
return C.GoString(&b[0])
}
Я поместил полный пример программы в Go Playground, но там нельзя собирать части C. (Я построил и запустил его в другой системе.)
Вы переназначаете значение указателя, хранящееся в
char *deviceID
. Вы не заполняете память, на которую указывает этот указатель.