In the docs 9i-11g charset of CLOB is UCS2, since 12c AL32UTF8. The note 257772.1 says AL16UTF16 is used since 10g. The main difference between UCS2 and AL16UTF16 is depending on the platform endian LE/BE.
Docs 21c:
Data in CLOB columns is stored in the AL16UTF16 character set when the database character set is multibyte, such as UTF8 or AL32UTF8.dbms_lob.getlength() has the clob argument type, that is, it entails an additional implicit conversion. It's better to use append() instead of writeappend(..getlength()). And it's even easier to use concatenation ||.
The description of DBMS_LOB should include a warning about the specific of complementary characters and what are characters for POSITION/AMOUNT arguments. SQL Reference should indicate that substr, length, etc. are unsafe when used with CLOB. Moreover, the result may depend on plsql_optimization_level.
declare
procedure p(lbl varchar2, cl clob) is
v varchar2(32767 byte);
begin
v := substr(cl,1,4);
dbms_output.put_line(lbl||'len='||length(v)||' lenb='||lengthb(v)||' hex='||utl_raw.cast_to_raw(v)||' str='||v);
exception when others then
dbms_output.put_line(lbl||sqlerrm);
end;
begin
pragma inline(p, 'yes'); -- default YES with plsql_optimize_level=3
p('error : ', to_clob('xyz'||chr(4036991128)));
pragma inline(p, 'no'); -- default NO with plsql_optimize_level<3
p('3 of 4 : ', to_clob('xyz'||chr(4036991128)));
end;
/
error : ORA-22831: Offset or offset+amount does not land on character boundary
3 of 4 : len=3 lenb=3 hex=78797A str=xyz