来自C String的Swift String具有正确的长度但内容不正确

时间:2016-06-12 17:01:07

标签: c++ ios swift

我遇到与this fellow类似的问题,但问题与该答案的原因不同。

我从C函数中获得const char *。这个函数实际上是用C ++实现的,它按值返回一个struct,它包含std::string c_str()的返回值,它是一个指向字符串内容的C指针。该字符串存在于静态内存中。由于指针被复制到返回中,我希望数据没问题。

根据Xcode的调试器,我得到的字符串实际上是我所期望的正确长度(请参阅下面的countAndFlags,正确的字符数为7),但内容是垃圾。这是一个UITableView方法,而且非常奇怪的是,如果我使用reloadData() TableView方法(我已经连接到一个按钮)然后具有name的有效内容,垃圾问题就会消失变量如下。然而,reloadData()的后续调用不会改变表中其他地方使用的任何数据,只有这个有问题的字符串垃圾才会消失。

enter image description here

这是第一遍的调试器,显示垃圾但是长度正确:

enter image description here

这是文本正确的下一个传递:

enter image description here

我无法理解这种行为。 struct s包含其他返回时未损坏的属性,但这个char *在第一次传递时是垃圾,但仍然是正确的长度。

我很欣赏这里发生的任何指示(没有双关语)。

最后,这是返回C字符串的函数的C ++代码:

scene getScene(sceneID s){
  static std::map<sceneID, std::vector<nodeID> > nodeArrays;
  auto ss = globalObjects.scenes.at(s);
  auto sceneroots = ss.roots;
  if (nodeArrays.count(s)>0) nodeArrays.at(s).clear();
  else confirmInsertion(nodeArrays.emplace(s, std::vector<nodeID>{}));
  auto v = nodeArrays.at(s);
  v.reserve(sceneroots.size());
  for (const auto&i:sceneroots) v.push_back(i);
  scene sr;
  sr.name=ss.name.c_str();
  sr.roots=vec2array(v);
  return sr;
}

如果我记录sr.name,它总是正确的。

以下是scene

  typedef struct {
    const char* name;
    arrayID roots;
  } scene;

2 个答案:

答案 0 :(得分:1)

你应该注意的一个问题是在C ++函数中:

鉴于这是您的scene定义:

typedef struct {
    const char* name;
    arrayID roots;
  } scene;

getScene C ++函数中,您正在执行此操作:

auto ss = globalObjects.scenes.at(s);

然后你会这样做:

  scene sr;
  sr.name = ss.name.c_str();
  //...
  return sr;

问题是,由于ss是局部变量,因此当函数返回时,分配给ss.name.c_str()的{​​{1}}指针将无效。访问此指针会导致未定义的行为。

问题的一个解决方法是将字符串的内容复制到客户端程序发送的缓冲区中,或者将内容复制到客户端可用的缓冲区中,并且不会失效。由于我不了解斯威夫特,你将不得不谈判如何实现这一目标。

另一种解决方案是确保您访问的是同一个sr.name,而不是std::string的副本:

std::string

现在返回对auto& ss = globalObjects.scenes.at(s);的引用。但是,当您在应用程序中稍后实际引用返回的scene值时,您会遇到确保scene中的字符串完全没有变异的问题。 C ++程序员不会长时间保持c_str()的返回值,因为这会导致难以诊断的错误。

底线是始终怀疑返回字符指针作为返回字符串数据的方法。 API返回字符串数据的常用方法是让客户端提供缓冲区,API使用字符串数据填充缓冲区,或者不太可能,API分配内存并返回指向已分配内存的指针(这会导致复杂化API必须通过某种方式释放内存以避免内存泄漏。)

答案 1 :(得分:0)

在您调用String.fromCString时,您似乎正在传递有效的C字符串,该字符串稍后会失效。如您所知,Swift String的长度是正确的,但是从调试器中您可以看到它已嵌入\0。我能想到的唯一方法是,如果C字符串最初是有效的,那么在Swift完成解析之前它就会被破坏。

来自http://www.cplusplus.com/reference/string/string/c_str/

  

返回的指针指向当前使用的内部数组   用于存储符合其值的字符的字符串对象。

     

通过进一步调用其他指针,可能会使返回的指针无效   修改对象的成员函数。

您应该在C ++代码中使用C字符串,以便在此之后知道它不能更改,然后您可以确认您的Swift代码是按预期工作的。