diff --git a/c++/src/capnp/encoding-test.c++ b/c++/src/capnp/encoding-test.c++
index a7a01ab982bc5cc120019b2cc0e3a9772c9ad9c6..e5c562951e268651c5e99ddf3f31a98740f7889b 100644
--- a/c++/src/capnp/encoding-test.c++
+++ b/c++/src/capnp/encoding-test.c++
@@ -1747,6 +1747,51 @@ TEST(Encoding, GlobalConstants) {
   }
 }
 
+TEST(Encoding, HasEmptyStruct) {
+  MallocMessageBuilder message;
+  auto root = message.initRoot<test::TestObject>();
+
+  EXPECT_EQ(1, root.totalSizeInWords());
+
+  EXPECT_FALSE(root.asReader().hasObjectField());
+  EXPECT_FALSE(root.hasObjectField());
+  root.initObjectField<test::TestEmptyStruct>();
+  EXPECT_TRUE(root.asReader().hasObjectField());
+  EXPECT_TRUE(root.hasObjectField());
+
+  EXPECT_EQ(1, root.totalSizeInWords());
+}
+
+TEST(Encoding, HasEmptyList) {
+  MallocMessageBuilder message;
+  auto root = message.initRoot<test::TestObject>();
+
+  EXPECT_EQ(1, root.totalSizeInWords());
+
+  EXPECT_FALSE(root.asReader().hasObjectField());
+  EXPECT_FALSE(root.hasObjectField());
+  root.initObjectField<List<int32_t>>(0);
+  EXPECT_TRUE(root.asReader().hasObjectField());
+  EXPECT_TRUE(root.hasObjectField());
+
+  EXPECT_EQ(1, root.totalSizeInWords());
+}
+
+TEST(Encoding, HasEmptyStructList) {
+  MallocMessageBuilder message;
+  auto root = message.initRoot<test::TestObject>();
+
+  EXPECT_EQ(1, root.totalSizeInWords());
+
+  EXPECT_FALSE(root.asReader().hasObjectField());
+  EXPECT_FALSE(root.hasObjectField());
+  root.initObjectField<List<TestAllTypes>>(0);
+  EXPECT_TRUE(root.asReader().hasObjectField());
+  EXPECT_TRUE(root.hasObjectField());
+
+  EXPECT_EQ(2, root.totalSizeInWords());
+}
+
 }  // namespace
 }  // namespace _ (private)
 }  // namespace capnp
diff --git a/c++/src/capnp/layout.c++ b/c++/src/capnp/layout.c++
index 8bdfcff891d0b71262090845db0ce0f809a54800..6b77d68d5ab2909bd0d6ffeabf4c1dbab2869f08 100644
--- a/c++/src/capnp/layout.c++
+++ b/c++/src/capnp/layout.c++
@@ -86,12 +86,43 @@ struct WirePointer {
     return reinterpret_cast<const word*>(this) + 1 +
         (static_cast<int32_t>(offsetAndKind.get()) >> 2);
   }
-  KJ_ALWAYS_INLINE(void setKindAndTarget(Kind kind, word* target)) {
+  KJ_ALWAYS_INLINE(void setKindAndTarget(Kind kind, word* target, SegmentBuilder* segment)) {
+    // Check that the target is really in the same segment, otherwise subtracting pointers is
+    // undefined behavior.  As it turns out, it's undefined behavior that actually produces
+    // unexpected results in a real-world situation that actually happened:  At one time,
+    // OrphanBuilder's "tag" (a WirePointer) was allowed to be initialized as if it lived in
+    // a particular segment when in fact it does not.  On 32-bit systems, where words might
+    // only be 32-bit aligned, it's possible that the difference between `this` and `target` is
+    // not a whole number of words.  But clang optimizes:
+    //     (target - (word*)this - 1) << 2
+    // to:
+    //     (((ptrdiff_t)target - (ptrdiff_t)this - 8) >> 1)
+    // So now when the pointers are not aligned the same, we can end up corrupting the bottom
+    // two bits, where `kind` is stored.  For example, this turns a struct into a far pointer.
+    // Ouch!
+    KJ_DREQUIRE(segment->containsInterval(
+        reinterpret_cast<word*>(this), reinterpret_cast<word*>(this + 1)));
+    KJ_DREQUIRE(segment->containsInterval(target, target));
     offsetAndKind.set(((target - reinterpret_cast<word*>(this) - 1) << 2) | kind);
   }
   KJ_ALWAYS_INLINE(void setKindWithZeroOffset(Kind kind)) {
     offsetAndKind.set(kind);
   }
+  KJ_ALWAYS_INLINE(void setKindAndTargetForEmptyStruct()) {
+    // This pointer points at an empty struct.  Assuming the WirePointer itself is in-bounds, we
+    // can set the target to point either at the WirePointer itself or immediately after it.  The
+    // latter would cause the WirePointer to be "null" (since for an empty struct the upper 32
+    // bits are going to be zero).  So we set an offset of -1, as if the struct were allocated
+    // immediately before this pointer, to distinguish it from null.
+    offsetAndKind.set(0xfffffffc);
+  }
+  KJ_ALWAYS_INLINE(void setKindForOrphan(Kind kind)) {
+    // OrphanBuilder contains a WirePointer, but since it isn't located in a segment, it should
+    // not have a valid offset (unless it is a FAR pointer).  We set its offset to -1 because
+    // setting it to zero would mean a pointer to an empty struct would appear to be a null pointer.
+    KJ_DREQUIRE(kind != FAR);
+    offsetAndKind.set(kind | 0xfffffffc);
+  }
 
   KJ_ALWAYS_INLINE(ElementCount inlineCompositeListElementCount() const) {
     return (offsetAndKind.get() >> 2) * ELEMENTS;
@@ -272,23 +303,16 @@ struct WireHelpers {
     //   segment belonging to the arena.  `ref` will be initialized as a non-far pointer, but its
     //   target offset will be set to zero.
 
-    if (amount == 0 * WORDS && kind == WirePointer::STRUCT) {
-      // Allocating a zero-sized struct.  If it happens to be allocated in the space immediately
-      // after the pointer, we'll have a problem:  the pointer will end up all-zero, so isNull()
-      // will be true.  This can lead to all kinds of weird behavior later on.  Since the target
-      // has zero-size anyway, we can really set the pointer to point anywhere, as long as it
-      // is in-bounds.  So, we can have the pointer point back at itself (an offset of -1).  This
-      // should be exceedingly rare in practice since empty structs are pretty useless.
-      //
-      // Note that the check for kind == WirePointer::STRUCT will hopefully cause this whole branch
-      // to be optimized away from all the call sites that are allocating non-structs.
-      ref->setKindAndTarget(WirePointer::STRUCT, reinterpret_cast<word*>(ref));
-      return reinterpret_cast<word*>(ref);
-    }
-
     if (orphanArena == nullptr) {
       if (!ref->isNull()) zeroObject(segment, ref);
 
+      if (amount == 0 * WORDS && kind == WirePointer::STRUCT) {
+        // Note that the check for kind == WirePointer::STRUCT will hopefully cause this whole
+        // branch to be optimized away from all the call sites that are allocating non-structs.
+        ref->setKindAndTargetForEmptyStruct();
+        return reinterpret_cast<word*>(ref);
+      }
+
       word* ptr = segment->allocate(amount);
 
       if (ptr == nullptr) {
@@ -306,19 +330,20 @@ struct WireHelpers {
 
         // Initialize the landing pad to indicate that the data immediately follows the pad.
         ref = reinterpret_cast<WirePointer*>(ptr);
-        ref->setKindAndTarget(kind, ptr + POINTER_SIZE_IN_WORDS);
+        ref->setKindAndTarget(kind, ptr + POINTER_SIZE_IN_WORDS, segment);
 
         // Allocated space follows new pointer.
         return ptr + POINTER_SIZE_IN_WORDS;
       } else {
-        ref->setKindAndTarget(kind, ptr);
+        ref->setKindAndTarget(kind, ptr, segment);
         return ptr;
       }
     } else {
       // orphanArena is non-null.  Allocate an orphan.
+      KJ_DASSERT(ref->isNull());
       auto allocation = orphanArena->allocate(amount);
       segment = allocation.segment;
-      ref->setKindWithZeroOffset(kind);
+      ref->setKindForOrphan(kind);
       return allocation.words;
     }
   }
@@ -790,7 +815,7 @@ struct WireHelpers {
 
     if (dstSegment == srcSegment) {
       // Same segment, so create a direct pointer.
-      dst->setKindAndTarget(srcTag->kind(), srcPtr);
+      dst->setKindAndTarget(srcTag->kind(), srcPtr, dstSegment);
 
       // We can just copy the upper 32 bits.  (Use memcpy() to comply with aliasing rules.)
       memcpy(&dst->upper32Bits, &srcTag->upper32Bits, sizeof(srcTag->upper32Bits));
@@ -816,7 +841,7 @@ struct WireHelpers {
         dst->farRef.set(farSegment->getSegmentId());
       } else {
         // Simple landing pad is just a pointer.
-        landingPad->setKindAndTarget(srcTag->kind(), srcPtr);
+        landingPad->setKindAndTarget(srcTag->kind(), srcPtr, srcSegment);
         memcpy(&landingPad->upper32Bits, &srcTag->upper32Bits, sizeof(srcTag->upper32Bits));
 
         dst->setFar(false, srcSegment->getOffsetTo(reinterpret_cast<word*>(landingPad)));
@@ -853,7 +878,7 @@ struct WireHelpers {
     useDefault:
       if (defaultValue == nullptr ||
           reinterpret_cast<const WirePointer*>(defaultValue)->isNull()) {
-        return initStructPointer(ref, segment, size);
+        return initStructPointer(ref, segment, size, orphanArena);
       }
       refTarget = copyMessage(segment, ref, reinterpret_cast<const WirePointer*>(defaultValue));
       defaultValue = nullptr;  // If the default value is itself invalid, don't use it again.
@@ -942,7 +967,8 @@ struct WireHelpers {
       StructSize elementSize, BuilderArena* orphanArena = nullptr)) {
     if (elementSize.preferredListEncoding != FieldSize::INLINE_COMPOSITE) {
       // Small data-only struct.  Allocate a list of primitives instead.
-      return initListPointer(ref, segment, elementCount, elementSize.preferredListEncoding);
+      return initListPointer(ref, segment, elementCount, elementSize.preferredListEncoding,
+                             orphanArena);
     }
 
     auto wordsPerElement = elementSize.total() / ELEMENTS;
@@ -976,7 +1002,7 @@ struct WireHelpers {
 
   static KJ_ALWAYS_INLINE(ListBuilder getWritableListPointer(
       WirePointer* origRef, word* origRefTarget, SegmentBuilder* origSegment, FieldSize elementSize,
-      const word* defaultValue)) {
+      const word* defaultValue, BuilderArena* orphanArena = nullptr)) {
     KJ_DREQUIRE(elementSize != FieldSize::INLINE_COMPOSITE,
              "Use getStructList{Element,Field}() for structs.");
 
@@ -1614,7 +1640,7 @@ struct WireHelpers {
       zeroObject(segment, ref);
     }
 
-    if (value.segment == nullptr) {
+    if (value == nullptr) {
       // Set null.
       memset(ref, 0, sizeof(*ref));
     } else if (value.tagAsPtr()->kind() == WirePointer::FAR) {
@@ -1631,14 +1657,17 @@ struct WireHelpers {
   }
 
   static OrphanBuilder disown(SegmentBuilder* segment, WirePointer* ref) {
-    if (ref->isNull()) {
-      return OrphanBuilder();
-    } else {
-      OrphanBuilder result(ref, segment,
-          ref->kind() == WirePointer::FAR ? nullptr : ref->target());
-      memset(ref, 0, sizeof(*ref));
-      return result;
+    OrphanBuilder result(ref, segment,
+        getWritableObjectPointer(segment, ref, nullptr).getLocation());
+
+    if (!ref->isNull() && ref->kind() != WirePointer::FAR) {
+      result.tagAsPtr()->setKindForOrphan(ref->kind());
     }
+
+    // Zero out the pointer that was disowned.
+    memset(ref, 0, sizeof(*ref));
+
+    return result;
   }
 
   // -----------------------------------------------------------------
@@ -2589,7 +2618,7 @@ OrphanBuilder OrphanBuilder::initStruct(BuilderArena* arena, StructSize size) {
   OrphanBuilder result;
   StructBuilder builder = WireHelpers::initStructPointer(result.tagAsPtr(), nullptr, size, arena);
   result.segment = builder.segment;
-  result.location = reinterpret_cast<word*>(builder.data);
+  result.location = builder.getLocation();
   return result;
 }
 
@@ -2599,23 +2628,18 @@ OrphanBuilder OrphanBuilder::initList(
   ListBuilder builder = WireHelpers::initListPointer(
       result.tagAsPtr(), nullptr, elementCount, elementSize, arena);
   result.segment = builder.segment;
-  result.location = reinterpret_cast<word*>(builder.ptr);
+  result.location = builder.getLocation();
   return result;
 }
 
 OrphanBuilder OrphanBuilder::initStructList(
     BuilderArena* arena, ElementCount elementCount, StructSize elementSize) {
-  if (elementSize.preferredListEncoding != FieldSize::INLINE_COMPOSITE) {
-    // Small data-only struct.  Allocate a list of primitives instead.
-    return initList(arena, elementCount, elementSize.preferredListEncoding);
-  } else {
-    OrphanBuilder result;
-    ListBuilder builder = WireHelpers::initStructListPointer(
-        result.tagAsPtr(), nullptr, elementCount, elementSize, arena);
-    result.segment = builder.segment;
-    result.location = reinterpret_cast<word*>(builder.ptr) - POINTER_SIZE_IN_WORDS;
-    return result;
-  }
+  OrphanBuilder result;
+  ListBuilder builder = WireHelpers::initStructListPointer(
+      result.tagAsPtr(), nullptr, elementCount, elementSize, arena);
+  result.segment = builder.segment;
+  result.location = builder.getLocation();
+  return result;
 }
 
 OrphanBuilder OrphanBuilder::initText(BuilderArena* arena, ByteCount size) {
@@ -2669,106 +2693,93 @@ OrphanBuilder OrphanBuilder::copy(BuilderArena* arena, Data::Reader copyFrom) {
 }
 
 StructBuilder OrphanBuilder::asStruct(StructSize size) {
+  KJ_DASSERT(tagAsPtr()->isNull() == (location == nullptr));
+
   StructBuilder result = WireHelpers::getWritableStructPointer(
-      tagAsPtr(), location, segment, size, nullptr);
+      tagAsPtr(), location, segment, size, nullptr, segment->getArena());
 
   // Watch out, the pointer could have been updated if the object had to be relocated.
-  if (tagAsPtr()->kind() == WirePointer::FAR) {
-    location = nullptr;
-  } else {
-    location = reinterpret_cast<word*>(result.data);
-  }
+  location = reinterpret_cast<word*>(result.data);
 
   return result;
 }
 
 ListBuilder OrphanBuilder::asList(FieldSize elementSize) {
+  KJ_DASSERT(tagAsPtr()->isNull() == (location == nullptr));
+
   ListBuilder result = WireHelpers::getWritableListPointer(
-      tagAsPtr(), location, segment, elementSize, nullptr);
+      tagAsPtr(), location, segment, elementSize, nullptr, segment->getArena());
 
   // Watch out, the pointer could have been updated if the object had to be relocated.
-  if (tagAsPtr()->kind() == WirePointer::FAR) {
-    location = nullptr;
-  } else {
-    location = reinterpret_cast<word*>(result.ptr);
-  }
+  // (Actually, currently this is not true for primitive lists, but let's not turn into a bug if
+  // it changes!)
+  location = result.getLocation();
 
   return result;
 }
 
 ListBuilder OrphanBuilder::asStructList(StructSize elementSize) {
+  KJ_DASSERT(tagAsPtr()->isNull() == (location == nullptr));
+
   ListBuilder result = WireHelpers::getWritableStructListPointer(
-      tagAsPtr(), location, segment, elementSize, nullptr);
+      tagAsPtr(), location, segment, elementSize, nullptr, segment->getArena());
 
   // Watch out, the pointer could have been updated if the object had to be relocated.
-  if (tagAsPtr()->kind() == WirePointer::FAR) {
-    location = nullptr;
-  } else if (result.step * ELEMENTS <= BITS_PER_WORD * WORDS) {
-    location = reinterpret_cast<word*>(result.ptr);
-  } else {
-    location = reinterpret_cast<word*>(result.ptr) - POINTER_SIZE_IN_WORDS;
-  }
+  location = result.getLocation();
 
   return result;
 }
 
 Text::Builder OrphanBuilder::asText() {
+  KJ_DASSERT(tagAsPtr()->isNull() == (location == nullptr));
+
   // Never relocates.
   return WireHelpers::getWritableTextPointer(tagAsPtr(), location, segment, nullptr, 0 * BYTES);
 }
 
 Data::Builder OrphanBuilder::asData() {
+  KJ_DASSERT(tagAsPtr()->isNull() == (location == nullptr));
+
   // Never relocates.
   return WireHelpers::getWritableDataPointer(tagAsPtr(), location, segment, nullptr, 0 * BYTES);
 }
 
 ObjectBuilder OrphanBuilder::asObject() {
+  KJ_DASSERT(tagAsPtr()->isNull() == (location == nullptr));
+
   ObjectBuilder result = WireHelpers::getWritableObjectPointer(
       segment, tagAsPtr(), location, nullptr);
 
   // Watch out, the pointer could have been updated if the object had to be relocated.
-  if (tagAsPtr()->kind() == WirePointer::FAR) {
-    location = nullptr;
-  } else {
-    switch (result.kind) {
-      case ObjectKind::STRUCT:
-        location = reinterpret_cast<word*>(result.structBuilder.data);
-        break;
-      case ObjectKind::LIST:
-        if (tagAsPtr()->listRef.elementSize() == FieldSize::INLINE_COMPOSITE) {
-          location = reinterpret_cast<word*>(result.listBuilder.ptr) - POINTER_SIZE_IN_WORDS;
-        } else {
-          location = reinterpret_cast<word*>(result.listBuilder.ptr);
-        }
-        break;
-      case ObjectKind::NULL_POINTER:
-        location = nullptr;
-        break;
-    }
-  }
+  location = result.getLocation();
 
   return result;
 }
 
 StructReader OrphanBuilder::asStructReader(StructSize size) const {
+  KJ_DASSERT(tagAsPtr()->isNull() == (location == nullptr));
   return WireHelpers::readStructPointer(
       segment, tagAsPtr(), location, nullptr, std::numeric_limits<int>::max());
 }
 
 ListReader OrphanBuilder::asListReader(FieldSize elementSize) const {
+  KJ_DASSERT(tagAsPtr()->isNull() == (location == nullptr));
   return WireHelpers::readListPointer(
       segment, tagAsPtr(), location, nullptr, elementSize, std::numeric_limits<int>::max());
 }
 
 Text::Reader OrphanBuilder::asTextReader() const {
+  KJ_DASSERT(tagAsPtr()->isNull() == (location == nullptr));
   return WireHelpers::readTextPointer(segment, tagAsPtr(), location, nullptr, 0 * BYTES);
 }
 
 Data::Reader OrphanBuilder::asDataReader() const {
+  KJ_DASSERT(tagAsPtr()->isNull() == (location == nullptr));
   return WireHelpers::readDataPointer(segment, tagAsPtr(), location, nullptr, 0 * BYTES);
 }
 
 ObjectReader OrphanBuilder::asObjectReader() const {
+  KJ_DASSERT(tagAsPtr()->isNull() == (location == nullptr));
   return WireHelpers::readObjectPointer(
       segment, tagAsPtr(), location, nullptr, std::numeric_limits<int>::max());
 }
@@ -2777,14 +2788,13 @@ void OrphanBuilder::euthanize() {
   // Carefully catch any exceptions and rethrow them as recoverable exceptions since we may be in
   // a destructor.
   auto exception = kj::runCatchingExceptions([&]() {
-    auto ref = reinterpret_cast<WirePointer*>(&tag);
-    if (ref->kind() == WirePointer::FAR) {
-      WireHelpers::zeroObject(segment, ref);
+    if (tagAsPtr()->kind() == WirePointer::FAR) {
+      WireHelpers::zeroObject(segment, tagAsPtr());
     } else {
-      WireHelpers::zeroObject(segment, reinterpret_cast<WirePointer*>(&tag), location);
+      WireHelpers::zeroObject(segment, tagAsPtr(), location);
     }
 
-    memset(ref, 0, sizeof(*ref));
+    memset(&tag, 0, sizeof(tag));
     segment = nullptr;
     location = nullptr;
   });
diff --git a/c++/src/capnp/layout.h b/c++/src/capnp/layout.h
index 5584500beeacd50bc19b27e7ea968ec892370488..5566da8475d97781c6cb51d7b53f900bf625880e 100644
--- a/c++/src/capnp/layout.h
+++ b/c++/src/capnp/layout.h
@@ -279,6 +279,10 @@ public:
   static StructBuilder getRoot(SegmentBuilder* segment, word* location, StructSize size);
   static void adoptRoot(SegmentBuilder* segment, word* location, OrphanBuilder orphan);
 
+  inline word* getLocation() { return reinterpret_cast<word*>(data); }
+  // Get the object's location.  Only valid for independently-allocated objects (i.e. not list
+  // elements).
+
   inline BitCount getDataSectionSize() const { return dataSize; }
   inline WirePointerCount getPointerSectionSize() const { return pointerCount; }
   inline Data::Builder getDataSectionAsBlob();
@@ -533,6 +537,17 @@ public:
       : segment(nullptr), ptr(nullptr), elementCount(0 * ELEMENTS),
         step(0 * BITS / ELEMENTS) {}
 
+  inline word* getLocation() {
+    // Get the object's location.  Only valid for independently-allocated objects (i.e. not list
+    // elements).
+
+    if (step * ELEMENTS <= BITS_PER_WORD * WORDS) {
+      return reinterpret_cast<word*>(ptr);
+    } else {
+      return reinterpret_cast<word*>(ptr) - POINTER_SIZE_IN_WORDS;
+    }
+  }
+
   inline ElementCount size() const;
   // The number of elements in the list.
 
@@ -722,6 +737,15 @@ struct ObjectBuilder {
   ObjectBuilder(ListBuilder listBuilder)
       : kind(ObjectKind::LIST), listBuilder(listBuilder) {}
 
+  inline word* getLocation() {
+    switch (kind) {
+      case ObjectKind::NULL_POINTER: return nullptr;
+      case ObjectKind::STRUCT: return structBuilder.getLocation();
+      case ObjectKind::LIST: return listBuilder.getLocation();
+    }
+    return nullptr;
+  }
+
   ObjectReader asReader() const;
 
   inline ObjectBuilder(ObjectBuilder& other) { memcpy(this, &other, sizeof(*this)); }
@@ -773,8 +797,8 @@ public:
   OrphanBuilder& operator=(const OrphanBuilder& other) = delete;
   inline OrphanBuilder& operator=(OrphanBuilder&& other);
 
-  inline bool operator==(decltype(nullptr)) const { return segment == nullptr; }
-  inline bool operator!=(decltype(nullptr)) const { return segment != nullptr; }
+  inline bool operator==(decltype(nullptr)) const { return location == nullptr; }
+  inline bool operator!=(decltype(nullptr)) const { return location != nullptr; }
 
   StructBuilder asStruct(StructSize size);
   // Interpret as a struct, or throw an exception if not a struct.
@@ -816,8 +840,7 @@ private:
   // FAR pointer.
 
   word* location;
-  // Pointer to the object.  Invalid if the tag is a FAR pointer (in which case you need to follow
-  // the FAR pointer instead).
+  // Pointer to the object, or nullptr if the pointer is null.
 
   inline OrphanBuilder(const void* tagPtr, SegmentBuilder* segment, word* location)
       : segment(segment), location(location) {
diff --git a/c++/src/capnp/orphan-test.c++ b/c++/src/capnp/orphan-test.c++
index 5cc94fe3b4e281a335a515d74ec7f0b02453bc71..175a746a9da8646beaf6b8eb2bedf6ba61d788f9 100644
--- a/c++/src/capnp/orphan-test.c++
+++ b/c++/src/capnp/orphan-test.c++
@@ -781,12 +781,103 @@ TEST(Orphans, FarPointer) {
   EXPECT_TRUE(orphan != nullptr);
   EXPECT_FALSE(orphan == nullptr);
 
-  KJ_DBG(orphan != nullptr, orphan == nullptr);
-
   checkTestMessage(orphan.getReader());
   checkTestMessage(orphan.get());
 }
 
+TEST(Orphans, UpgradeStruct) {
+  MallocMessageBuilder builder;
+  auto root = builder.initRoot<test::TestObject>();
+
+  auto old = root.initObjectField<test::TestOldVersion>();
+  old.setOld1(1234);
+  old.setOld2("foo");
+
+  auto orphan = root.disownObjectField<test::TestNewVersion>();
+
+  // Relocation has not occurred yet.
+  old.setOld1(12345);
+  EXPECT_EQ(12345, orphan.getReader().getOld1());
+  EXPECT_EQ("foo", old.getOld2());
+
+  // This will relocate the struct.
+  auto newVersion = orphan.get();
+
+  EXPECT_EQ(0, old.getOld1());
+  EXPECT_EQ("", old.getOld2());
+
+  EXPECT_EQ(12345, newVersion.getOld1());
+  EXPECT_EQ("foo", newVersion.getOld2());
+}
+
+TEST(Orphans, UpgradeStructList) {
+  MallocMessageBuilder builder;
+  auto root = builder.initRoot<test::TestObject>();
+
+  auto old = root.initObjectField<List<test::TestOldVersion>>(2);
+  old[0].setOld1(1234);
+  old[0].setOld2("foo");
+  old[1].setOld1(4321);
+  old[1].setOld2("bar");
+
+  auto orphan = root.disownObjectField<List<test::TestNewVersion>>();
+
+  // Relocation has not occurred yet.
+  old[0].setOld1(12345);
+  EXPECT_EQ(12345, orphan.getReader()[0].getOld1());
+  EXPECT_EQ("foo", old[0].getOld2());
+
+  // This will relocate the struct.
+  auto newVersion = orphan.get();
+
+  EXPECT_EQ(0, old[0].getOld1());
+  EXPECT_EQ("", old[0].getOld2());
+
+  EXPECT_EQ(12345, newVersion[0].getOld1());
+  EXPECT_EQ("foo", newVersion[0].getOld2());
+  EXPECT_EQ(4321, newVersion[1].getOld1());
+  EXPECT_EQ("bar", newVersion[1].getOld2());
+}
+
+TEST(Orphans, DisownNull) {
+  MallocMessageBuilder builder;
+  auto root = builder.initRoot<TestAllTypes>();
+
+  {
+    Orphan<TestAllTypes> orphan = root.disownStructField();
+    EXPECT_TRUE(orphan == nullptr);
+
+    checkTestMessageAllZero(orphan.getReader());
+    EXPECT_TRUE(orphan == nullptr);
+
+    // get()ing the orphan allocates an object, for security reasons.
+    checkTestMessageAllZero(orphan.get());
+    EXPECT_FALSE(orphan == nullptr);
+  }
+
+  {
+    Orphan<List<int32_t>> orphan = root.disownInt32List();
+    EXPECT_TRUE(orphan == nullptr);
+
+    EXPECT_EQ(0, orphan.getReader().size());
+    EXPECT_TRUE(orphan == nullptr);
+
+    EXPECT_EQ(0, orphan.get().size());
+    EXPECT_TRUE(orphan == nullptr);
+  }
+
+  {
+    Orphan<List<TestAllTypes>> orphan = root.disownStructList();
+    EXPECT_TRUE(orphan == nullptr);
+
+    EXPECT_EQ(0, orphan.getReader().size());
+    EXPECT_TRUE(orphan == nullptr);
+
+    EXPECT_EQ(0, orphan.get().size());
+    EXPECT_TRUE(orphan == nullptr);
+  }
+}
+
 }  // namespace
 }  // namespace _ (private)
 }  // namespace capnp
diff --git a/c++/src/capnp/orphan.h b/c++/src/capnp/orphan.h
index 24a5fcd3db195d160ac3d1003e58afc0ed4d0350..6cb945abc1ce23665f66a90c6c65d29475a2741b 100644
--- a/c++/src/capnp/orphan.h
+++ b/c++/src/capnp/orphan.h
@@ -53,6 +53,12 @@ public:
   Orphan& operator=(Orphan&&) = default;
 
   inline typename T::Builder get();
+  // Get the underlying builder.  If the orphan is null, this will allocate and return a default
+  // object rather than crash.  This is done for security -- otherwise, you might enable a DoS
+  // attack any time you disown a field and fail to check if it is null.  In the case of structs,
+  // this means that the orphan is no longer null after get() returns.  In the case of lists,
+  // no actual object is allocated since a simple empty ListBuilder can be returned.
+
   inline typename T::Reader getReader() const;
 
   inline bool operator==(decltype(nullptr)) const { return builder == nullptr; }
diff --git a/c++/src/capnp/test.capnp b/c++/src/capnp/test.capnp
index d2b9f819bb792510cfc611e3b1f8b2dab52523fa..b9150d74b68ef59c85440232e96b39fc323d4d0f 100644
--- a/c++/src/capnp/test.capnp
+++ b/c++/src/capnp/test.capnp
@@ -162,6 +162,9 @@ struct TestDefaults {
 
 struct TestObject {
   objectField @0 :Object;
+
+  # Do not add any other fields here!  Some tests rely on objectField being the last pointer
+  # in the struct.
 }
 
 struct TestOutOfOrder {
@@ -478,6 +481,8 @@ struct TestStructUnion {
   }
 }
 
+struct TestEmptyStruct {}
+
 struct TestConstants {
   const voidConst      :Void    = void;
   const boolConst      :Bool    = true;