Home / Alt manpages / clang-reorder-fields-20(1)

  • clang-reorder-fields-20(1)
  • User command
  • linux

Reorder C and C++ Struct Fields Safely with clang-reorder-fields

clang-reorder-fields rewrites a struct's field order and can silently break a compiled program's memory layout. The examples use the installed LLVM 20 tool, version 20.1.8, from package clang-tools-20.

  • Time: about fifteen minutes, plus time to review the resulting diff.
  • Needs: a shell, a source file, and a clean or otherwise recoverable working tree.
  • Not covered: this tool does not optimise a binary in place. Keep the preview output, make a backup or use version control, and compile and test the result before accepting it.

1. Check the installed command

Start with read-only checks. No elevated privileges needed:

$ command -v clang-reorder-fields-20
/usr/bin/clang-reorder-fields-20
$ clang-reorder-fields-20 --version
Ubuntu LLVM version 20.1.8
  Optimized build.
$ dpkg-query -W -f='${Package} ${Version}\n' clang-tools-20
clang-tools-20 1:20.1.8~++20250804090239+87f0227cb601-1~exp1~20250804210352.139

The installed manual describes the command as clang-reorder-fields [options] <source0> [... <sourceN>]. The packaged executable has the versioned name, so use clang-reorder-fields-20 in scripts unless you have deliberately provided another command name.

2. Inspect the record and choose an exact order

--record-name identifies the struct or class. --fields-order takes a comma-separated list containing every field, once, in its desired order. Names must match the definition. For a namespaced C++ type, use its fully qualified name, such as ::network::Packet.

Suppose packet.cpp contains this record:

struct Packet {
  int payload;
  char kind;
  short flags;
};

To request kind, then flags, then payload, pass one comma-separated value:

$ clang-reorder-fields-20 \
    --record-name=Packet \
    --fields-order=kind,flags,payload \
    packet.cpp

Checkpoint

The command should print the rewritten source to standard output and leave packet.cpp unchanged. Without -i, redirecting that output to a new file is a safe way to inspect it:

$ clang-reorder-fields-20 --record-name=Packet \
    --fields-order=kind,flags,payload packet.cpp > packet.cpp.new
$ diff -u packet.cpp packet.cpp.new
--- packet.cpp
+++ packet.cpp.new
@@
+  char kind;
+  short flags;
   int payload;
-  char kind;
-  short flags;
}

The program may print compilation-database diagnostics when no database is available, then continue with no compiler flags. That is not the same as a successful semantic check. If the source needs project-specific include paths or language settings, supply them explicitly or use a compilation database in the next step.

3. Supply the same compiler context as the project

Use -p with the directory containing compile_commands.json. This keeps the tool's parsing context aligned with the project:

$ clang-reorder-fields-20 -p build \
    --record-name=Packet \
    --fields-order=kind,flags,payload \
    src/packet.cpp > src/packet.cpp.new

For a small standalone C++20 example, an extra compiler argument can select the language standard:

$ clang-reorder-fields-20 --extra-arg=-std=c++20 \
    --record-name=Packet \
    --fields-order=kind,flags,payload packet.cpp > packet.cpp.new

--extra-arg appends an argument and --extra-arg-before prepends one. Use them for the minimum context needed to parse the file. Do not copy a whole build command into these options without checking how the shell will split it.

4. Review initialisers and layout changes

clang-reorder-fields can update aggregate initialisers and, for C++, constructor initialiser lists. It also handles C++20 designated initialisers when the source is parsed as C++20. That is useful, but it does not make the change risk-free:

struct Packet {
  int payload;
  char kind;
  short flags;
};

Packet packet = { 42, 'D', 3 };
  • Check every changed initialiser. After reordering, positional aggregate values must follow the new declaration order, and every caller that depends on the layout needs review.
  • Watch member-initialiser order in C++. Members are initialised in declaration order, not the order written in the constructor. The tool can warn when a reordered member is used before it has been initialised, but the warning does not repair the logic.
  • Do not assume a smaller object is guaranteed. Field order can reduce padding on one target and produce a different result on another.

Check representative targets with a test or a deliberately instrumented build, for example:

$ clang++ -std=c++20 -Wall -Wextra -pedantic -c packet.cpp -o /tmp/packet.o
$ git diff --check
$ git diff -- packet.cpp

The compile command is ordinary and can run as your normal user. Use sudo only if your project directory itself is inaccessible, and fix ownership or permissions rather than making the whole build run as root.

5. Apply the change only after the preview passes

Warning

-i overwrites the source file. It is an in-place edit, not a dry run. In a version-controlled checkout, inspect the working tree first:

$ git status --short
$ cp --preserve=all packet.cpp packet.cpp.before-reorder
$ clang-reorder-fields-20 -i \
    --record-name=Packet \
    --fields-order=kind,flags,payload \
    packet.cpp
$ git diff --check
$ git diff -- packet.cpp

If the preview was written to packet.cpp.new, you can instead promote it after review:

$ cmp -s packet.cpp.new packet.cpp; echo "comparison status: $?"
$ mv packet.cpp.new packet.cpp

Recovery

If the in-place result is wrong, restore the backup before doing anything else:

$ cp --preserve=all packet.cpp.before-reorder packet.cpp
$ git diff -- packet.cpp

Once the new version is compiled and tested, remove the backup only as a deliberate cleanup step. That deletion is irreversible outside version control.

6. Diagnose the likely failures

  • Missing or misspelled record name. The tool cannot find the definition; check namespaces and spelling rather than guessing.
  • Wrong number of field names. A field-order list with the wrong count is invalid. Do not separate names with spaces, since each space-separated value becomes another command-line argument or source path.
  • Unsupported declarations. Preprocessor directives between fields, macros that expand to multiple fields, and other unsupported forms can prevent reordering. A flexible array member in C must remain last, so an order that moves it should be rejected. Treat these as a reason to review the design manually, not as an invitation to force the edit.
  • A compiler diagnostic or constructor-initialiser warning. Preserve it with the preview and investigate the source location. A zero exit status means the tool completed its rewrite; it does not prove the resulting program is correct.

Done means

  • Version checked: the installed executable and package version were confirmed.
  • Order specified exactly: the record name and comma-separated field order contain exact, complete names.
  • Preview reviewed: before any in-place edit.
  • Context supplied: compiler context was given with -p or an explicit extra argument when required.
  • Risks reviewed: initialisers, layout assumptions and warnings were checked.
  • Verified working: the changed source passes the project's compile and test checks, and recovery instructions are available.