Choose Perl's C Abstractions When Writing XS Code
You will finish with a practical replacement checklist for C or XS code that runs inside Perl: use PerlIO* for streams, Perl's allocation macros for memory, SV APIs for strings, and Perl's character or numeric helpers where the local C library would be the wrong layer. This is a developer guide, not a Perl script tutorial. Allow about 20 minutes to apply the checklist to a small patch.
The route
Jump straight to the step you need, or tick off Done means at the end.
You need Perl development headers and a working C or XS build. The examples below describe calls and macro shapes from the installed perlclib manual on this machine, generated for Perl 5.38.2. They are reference snippets for code compiled into Perl or an extension, not commands to paste into a shell. No elevated privileges are required.
1. Confirm which documentation and headers you are targeting
Start by checking the interpreter and the documentation path before choosing an API. Perl's internal interfaces are version-sensitive, and a web page for another release can contain a different reference card.
$ perl -V:version
version='5.38.2';
$ perldoc -l perlclib
/usr/share/perl/5.38/pod/perlclib.pod
The installed manpage identifies itself as Perl 5.38.2. Keep that version beside your patch notes. If you are building against another Perl, read that installation's perlclib, perlapio, perlapi and perlguts documents as well.
Checkpoint
If perldoc -l perlclib fails, stop and install or select the matching Perl documentation and development package through your normal packaging process. Do not copy internal declarations from an unrelated host.
2. Replace stdio streams with PerlIO
When the code is part of the interpreter or an extension, do not assume that a C FILE * is the stream abstraction Perl wants. The installed reference maps standard streams and common stdio operations to PerlIO:
PerlIO *in = PerlIO_stdin();
PerlIO *out = PerlIO_stdout();
PerlIO *err = PerlIO_stderr();
PerlIO *fh = PerlIO_open(path, mode);
PerlIO_printf(fh, "%s\n", message);
PerlIO_flush(fh);
PerlIO_close(fh);
The important change is the type and the ownership path. Use PerlIO_open, PerlIO_flush, PerlIO_close, PerlIO_getc, PerlIO_putc and the related functions listed by perlclib. For a line read, the manual points to sv_gets; there is no direct PerlIO equivalent of C's fgets. For byte buffers, PerlIO_read and PerlIO_write take a byte count rather than C's element-size and element-count pair.
Do not use the deprecated PerlIO_reopen mapping as a new design. If a patch needs to reopen a stream, check the matching perlapio documentation for the supported interface in the Perl version you build against.
Checkpoint
Search the patch for FILE *, fopen, fprintf, fgets and fclose. Each occurrence should either be outside Perl-facing code for a documented reason, or have a deliberate PerlIO replacement.
3. Use Perl's allocation and copy macros
Perl tracks memory allocated for its own data structures. The reference card maps the familiar C calls to macros that carry Perl's allocation and typing conventions:
char *buffer = NULL;
Newx(buffer, length, char);
Copy(source, buffer, length, char);
Renew(buffer, larger_length, char);
Safefree(buffer);
Zero(record, 1, struct record);
Newx replaces a typed malloc, Newxz is the zeroed allocation form, and Renew replaces a typed realloc. Use Copy and Move instead of memcpy and memmove, but watch the argument order: the source comes before the destination. StructCopy is the matching form for a structure, and Safefree replaces free.
Do not mechanically replace every zeroing operation with Newxz. The manual also documents PoisonNew, PoisonFree and Poison for development checks that make accidental use of uninitialised or freed data fail earlier. Use poisoning only where the surrounding build and debugging policy support it.
Safety boundary
Never pass an untrusted or unchecked size into an allocation or copy. Validate multiplication and conversion before the macro call, and keep the element type consistent with the count. A macro does not make an invalid length safe.
4. Keep strings and character tests in Perl's model
For Perl values, prefer SV operations rather than extracting a raw string, editing it with C string functions, and trying to rebuild the value. The installed mapping is:
sv_setpv(sv, text);
sv_setpvn(sv, text, length);
sv_catpv(sv, suffix);
sv_catpvn(sv, suffix, suffix_length);
sv_setpvf(sv, "%s:%d", label, number);
Use sv_len for an SV length, sv_catpvf when formatting onto an existing SV, and sv_vcatpvfn when the arguments arrive as a va_list. For raw strings, instr replaces strstr; strEQ, strNE, strLE and strGT express comparisons, while memEQ and memNE express buffer comparisons.
Choose the character family deliberately. The ASCII macros such as isALPHA and isDIGIT are for known ASCII input. The Latin-1 forms add the documented ISO-8859-1 interpretation, and the locale forms such as isALPHA_LC follow locale behaviour. Do not use an ASCII test merely because it is familiar when the input can contain wider or encoded characters. The local perlapi documentation covers the wider and UTF-8 cases.
5. Parse numbers and process environment through Perl
For numeric input, avoid treating atoi as validation. The reference recommends grok_atoUV for a strict unsigned decimal parse, followed by a range check before casting:
UV value;
char *end = input_end;
if (grok_atoUV(input, &value, &end) && value <= INT_MAX) {
int result = (int)value;
/* continue from end */
} else {
/* reject the input */
}
That helper deliberately rejects negative input and leading whitespace. The manual also lists Atof, Strtod, Strtol and Strtoul, and warns against disguised forms such as Atol and Atoul. For process integration, use PerlEnv_getenv and my_setenv; do not substitute system casually. The documented direction is to inspect Perl's system implementation or use my_popen.
Similarly, use my_exit rather than calling C exit from code that must leave Perl cleanly. For signals, the listed replacement is rsignal. For jump handling, the manual says to use Perl's JMPENV stack instead of the setjmp.h functions.
6. Review the patch before building
Run a focused search, then compile and test against the same Perl installation. These checks are ordinary and do not change system state:
$ rg -n '\b(FILE|malloc|calloc|realloc|free|memcpy|memmove|strcpy|strcat|atoi|system|exit|setjmp|signal)\b' src xs
$ make test
The search is a review aid, not a proof. Some matches will be comments, platform shims or intentionally independent code. Investigate each one and record why a direct libc call remains. The build and test output is project-specific; a passing test suite is the expected checkpoint.
If you changed an allocation or stream type, recovery is a source-level revert of that patch followed by a clean rebuild. Do not remove a working Perl installation or headers to resolve a compile error. First compare the declarations and macros supplied by the build's actual perl and include paths.
Done means
- The patch targets the documented Perl version and its local
perlclibreference. - Perl-facing streams use
PerlIO*, not assumedFILE *handles. - Perl-owned memory uses the documented allocation, copy and free conventions with checked sizes.
- SV strings, character classes and numeric input use APIs matching their data model.
- Environment, process exit, signals and jump handling do not bypass Perl's abstractions without a recorded reason.
- The focused review and the project's normal build tests pass against the intended Perl headers.