访问从 MemorySegment 读取的指针时,出现“IndexOutOfBoundsException:对段的越界访问...”

Abd*_*azi 4 java project-panama java-21 java-ffm

我正在尝试测试 Java 21 的外部函数和内存功能。这是我的代码:

\n
public static void main(String[] args) {\n        // 1. Find foreign function on the C library path\n        Linker linker = Linker.nativeLinker();\n        SymbolLookup stdlib = linker.defaultLookup();\n        MethodHandle radixsort = linker.downcallHandle(stdlib.find("radixsort").orElseThrow(), FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.JAVA_CHAR));\n        // 2. Allocate on-heap memory to store four strings\n        String[] javaStrings = {"mouse", "cat", "dog", "car"};\n\n        // 3. Use try-with-resources to manage the lifetime of off-heap memory\n        try (Arena offHeap = Arena.ofConfined()) {\n            // 4. Allocate a region of off-heap memory to store four pointers\n            MemorySegment pointers = offHeap.allocateArray(ValueLayout.ADDRESS, javaStrings.length);\n            // 5. Copy the strings from on-heap to off-heap\n            for (int i = 0; i < javaStrings.length; i++) {\n                MemorySegment cString = offHeap.allocateUtf8String(javaStrings[i]);\n                pointers.setAtIndex(ValueLayout.ADDRESS, i, cString);\n                assert cString.getUtf8String(0).length() > 0;\n            }\n            // 6. Sort the off-heap data by calling the foreign function\n            radixsort.invoke(pointers, javaStrings.length, MemorySegment.NULL, '\\0');\n            // 7. Copy the (reordered) strings from off-heap to on-heap\n            for (int i = 0; i < javaStrings.length; i++) {\n                MemorySegment cString = pointers.getAtIndex(ValueLayout.ADDRESS, i);\n                javaStrings[i] = cString.getUtf8String(0);\n            }\n        } // 8. All off-heap memory is deallocated here\n        catch (Throwable e) {\n            throw new RuntimeException(e);\n        }\n        assert Arrays.equals(javaStrings, new String[]{"car", "c\xe2\x88\x9eat", "dog", "mouse"});  // true\n    }\n
Run Code Online (Sandbox Code Playgroud)\n

但我遇到了这个错误:

\n

Exception in thread "main" java.lang.RuntimeException: java.lang.IndexOutOfBoundsException: Out of bound access on segment MemorySegment{ heapBase: Optional.empty address:105553153094096 limit: 0 }; new offset = 0; new length = 1

\n

在线:

\n

javaStrings\\[i\\] = cString.getUtf8String(0);\n似乎MemorySegment cString = pointers.getAtIndex(ValueLayout.ADDRESS, i);返回一个 byteSize = 0 的 MemorySegment

\n

当我cString.getUtf8String(0);在原始 cString 上使用时,我没有遇到错误。

\n

看来pointers.setAtIndex并pointers.getAtIndex没有保留MemorySegment的byteSize。?

\n

Jor*_*nee 6

看来pointers.setAtIndex和pointers.getAtIndex不保留MemorySegment byteSize。?

对,那是正确的。该地址作为普通内存地址存储到堆外内存中。尺寸已下降。因此,当从堆外内存读回地址时,创建的内存段的大小为 0。

MemorySegment 的 javadoc中也对此进行了解释:

当与外部函数交互时,这些函数通常会分配一个内存区域并返回指向该区域的指针。使用内存段对内存区域进行建模具有挑战性,因为 Java 运行时无法了解该区域的大小。只有存储在指针中的区域起始地址可用。

...

MemorySegment API 使用零长度内存段来表示:

  • 从外部函数返回的指针;
  • 由外部函数传递给上行调用存根的指针;和
  • 从内存段读取的指针(更多内容见下文)。

您的用例是这三个用例中的后者。

您必须MemorySegment::reinterpret显式地调整段的大小。由于对于 C 字符串,实际大小未知(由空终止符指示),因此您可以调整段大小以使其可Long.MAX_VALUE访问:

// 7. Copy the (reordered) strings from off-heap to on-heap
for (int i = 0; i < javaStrings.length; i++) {
    MemorySegment cString = pointers.getAtIndex(ValueLayout.ADDRESS, i);
    cString = cString.reinterpret(Long.MAX_VALUE); // <----
    javaStrings[i] = cString.getUtf8String(0);
}
Run Code Online (Sandbox Code Playgroud)

或者,您可以创建一个AddressLayout具有所需大小的目标布局,以避免单独调用reinterpret:

AddressLayout UNBOUNDED_POINTER = ValueLayout.ADDRESS
        .withTargetLayout(MemoryLayout.sequenceLayout(Long.MAX_VALUE, ValueLayout.JAVA_BYTE));
Run Code Online (Sandbox Code Playgroud)

然后用它来取消引用:

// 7. Copy the (reordered) strings from off-heap to on-heap
for (int i = 0; i < javaStrings.length; i++) {
    MemorySegment cString = pointers.getAtIndex(UNBOUNDED_POINTER, i);
    // no reinterpret needed
    javaStrings[i] = cString.getUtf8String(0);
}
Run Code Online (Sandbox Code Playgroud)