Why shouldn't I use DDS_String_dup() to initialize string data?

Note: This solution applies to RTI Data Distribution Service 4.x.
The description in this FAQ only applies to data instances that are allocated with FooTypeSupport::create_data(), which pre-allocates the strings. (It does not apply to situations in which the data instance is allocated on the stack, or with malloc, in which case strings are not pre-allocated.)
 

If the data instance is created with FooTypeSupport::create_data(), the strings are pre-allocated already, and therefore we do not recommend using DDS_String_dup() to initialize the string fields.If you are considering using DDS_String_dup() to initialize string data, please be aware of the following:

  • In general, DDS strings have the same semantics as sequences. In other words, if a type contains a string<128>, then after the type is allocated the type "owns" the storage for the string. In RTI's implementation, there's no particular problem with swapping out the string pointer for some other allocated pointer, and in fact in Java you need to if you're going to do anything useful (since java.lang.String objects are immutable). But in C/C++ you can definitely get yourself in trouble if you don't pay careful attention.

There are several pitfalls you must watch out for:

  • DDS_String_dup() allocates storage for a copy of the string, and copies the string to that storage. So it is less efficient than simple copying; you need to deal with the heap allocator. If you can just copy the string into the already-allocated string storage, instead of managing the storage yourself, it is both faster and simpler. There are no obvious cases where DDS_String_dup() is preferable to using strncpy() to copy data into an existing instance.

  • You may be tempted to use this to get around fixed limitations on the size of your strings. So that after declaring something as a string<128>, you then go and allocate more storage for it using DDS_String_dup() and stick it into the instance. This is not a good idea. The reason why we use/require sequence limits is so that an instance object can figure out its serialization size and make sure it is consistent with the QoS and transport properties at object creation time. If you bypass those mechanisms, the object might think it will always fit, only to discover at run-time that it won't. At that point it is too late for you to set up large data support or anything else; you will just get dropped data and, possibly, strange error messages about serialization. So, if you try this, be extremely careful about your configuration. A better piece advice is not to do it at all.

Obviously, if you do allocate memory using DDS_String_dup(), you must later free it using DDS_String_free(). You must also free the initially allocated storage. However, do not free the storage and then leave around the pointer. In other words, don't do this:

  // initialize:
  instance = SomeTypeSupport::create_data();
  DDS_String_free(instance->msg);

  // whenever we're writing:
  instance->msg = DDS_String_dup("la la la");
  writer->write(*instance, 0);
  DDS_String_free(instance->msg);

  // finalize:
  SomeTypeSupport::delete_data(instance);
  // oops!

With the above code, delete_data() will have freed the stale instance->msg pointer again, possibly corrupting the heap in the process.
The following is a better choice:

  // initialize:
  instance = SomeTypeSupport::create_data();

  // whenever we're writing:
  DDS_String_free(instance->msg);
  instance->msg = DDS_String_dup("la la la");
  // handle allocation errors!
  writer->write(*instance, 0);

  // finalize:
  SomeTypeSupport::delete_data(instance);

The following will also work:

// initialize:
instance = SomeTypeSupport::create_data();
DDS_String_free(instance->msg);
instance->msg = NULL;

// whenever we're writing:
instance->msg = DDS_String_dup("la la la");
writer->write(*instance, 0);
DDS_String_free(instance->msg); instance->msg = NULL;

// finalize:
SomeTypeSupport::delete_data(instance);

Never mix DDS_String_dup() with copying into the buffer. Consider the following:

  instance = SomeTypeSupport::create_data();
  DDS_String_free(instance->msg);
  instance->msg = DDS_String_dup("la!");
  // storage for msg is now a 4-byte buffer('l', 'a', '!', '\0')
  strcpy(instance->msg, "la la la la!");
  // buffer overrun!

 

Programming Language: