peczenyj/structalign

GitHub: peczenyj/structalign

Stars: 6 | Forks: 0

# structalign [![Latest release](https://img.shields.io/github/release/peczenyj/structalign.svg)](https://github.com/peczenyj/structalign/releases/latest) ![Go Version](https://img.shields.io/badge/Go-%3E%3D%201.25-%23007d9c) [![GoDoc](https://pkg.go.dev/badge/github.com/peczenyj/structalign)](http://pkg.go.dev/github.com/peczenyj/structalign) [![CI](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/2b805723f8192626.svg)](https://github.com/peczenyj/structalign/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/peczenyj/structalign/graph/badge.svg)](https://codecov.io/gh/peczenyj/structalign) [![Report card](https://goreportcard.com/badge/github.com/peczenyj/structalign)](https://goreportcard.com/report/github.com/peczenyj/structalign) [![CodeQL](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/4512d67ba2192632.svg)](https://github.com/peczenyj/structalign/actions/workflows/github-code-scanning/codeql) [![Dependency Review](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/d99fdfb4d5192639.svg)](https://github.com/peczenyj/structalign/actions/workflows/dependency-review.yml) [![License](https://img.shields.io/github/license/peczenyj/structalign)](./LICENSE) [![GitHub Release Date](https://img.shields.io/github/release-date/peczenyj/structalign.svg)](https://github.com/peczenyj/structalign/releases/latest) [![Last commit](https://img.shields.io/github/last-commit/peczenyj/structalign.svg)](https://github.com/peczenyj/structalign/commit/HEAD) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/peczenyj/structalign/blob/main/CONTRIBUTING.md#pull-request-process) [![SLSA Build Level 2](https://img.shields.io/badge/SLSA-Build_L2-green.svg)](https://github.com/peczenyj/structalign/attestations) [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/peczenyj/structalign/badge)](https://scorecard.dev/viewer/?uri=github.com/peczenyj/structalign) [![OpenSSF Best Practices](https://bestpractices.coreinfrastructure.org/projects/13027/badge)](https://bestpractices.coreinfrastructure.org/projects/13027) [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge-flat.svg)](https://github.com/avelino/awesome-go#code-analysis) A read-only companion to `golang.org/x/tools`'s `fieldalignment`: it shows the memory-optimal struct as a unified or side-by-side diff built for human review, rather than rewriting your files or emitting a machine-applicable patch, and can also print any struct's offset/size/align/padding layout. The analysis comes straight from the upstream analyzer, so results match `fieldalignment` exactly — only the presentation is new. ![structalign colored unified-diff output against the bundled sample](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/32cb8cc606192645.png) ## Quick start Install: go install github.com/peczenyj/structalign@latest Or grab a prebuilt binary for your OS/arch from the [Releases](https://github.com/peczenyj/structalign/releases) page. Check the installed version with `structalign -version`. Then point it at a file, a package, or any Go package pattern: structalign ./... # every package in the module It accepts whatever the `go` tool does — `./...`, import paths, directories, and single `.go` files — and you can pass several at once. By default it skips **generated files** (`// Code generated … DO NOT EDIT.`) and `_test.go` files; use `-generated` / `-tests` to include them (see [Scanning scope](#scanning-scope)). Pointed at the bundled sample (`./_example`), it reports the reordering and exits non-zero so it can gate CI: $ structalign -type=Mixed ./_example _example/types.go:6:12: Mixed: struct of size 24 could be 16, saving 8 bytes (33.33% smaller) type Mixed struct { + B int64 A bool - B int64 C bool } $ echo $? 1 ## Why it exists `golang.org/x/tools/.../fieldalignment` can already detect a misaligned struct and rewrite it for you. It offers three things: - **report** (default) — prints a terse message like `struct of size 24 could be 16` and nothing else; - **`-fix`** — rewrites your source in place; - **`-fix -diff`** — instead of writing, prints the change as a unified patch. So the change *can* be shown — but only as a patch built for `patch`/`git apply`, not for a person to read. It answers "how do I apply this?", not "what would the optimal struct look like, and is the saving worth it?" And none of these modes let you inspect a struct's *existing* layout — offsets, sizes, padding — at all. `structalign` is the readability layer over that same upstream analysis: it shows the reordering as output meant for people — a review-oriented diff (unified or side-by-side, with color, summary, threshold, and tag-stripping) — plus a per-field layout inspector. | | [fieldalignment][fa] | [betteralign][ba] | [structlayout][sl] | **structalign** | |------------------------------|:--:|:--:|:--:|:--:| | Report the misalignment | ✅ | ✅ | — | ✅ | | **Human-readable** diff | — | — | — | ✅ | | Machine-applicable patch | `-fix -diff` | `-fix -diff` | — | — | | Rewrite files in place | `-fix` | `-fix` | — | — | | Inspect field layout | — | — | ✅ | ✅ | | CI-friendly exit code | ✅ | ✅ | — | ✅ | ## Usage `structalign` is a read-only companion to [`fieldalignment`](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/fieldalignment): it prints the reordered struct plus a diff (or, with `-inspect`, a struct's memory layout) for review, and never edits files. The analysis matches `fieldalignment` exactly; for an in-place rewrite, use `fieldalignment -fix`. `packages` are whatever the go tool understands: `./...`, import paths, directories, or single `.go` files. Generated and `_test.go` files are skipped unless `-generated` / `-tests` are given; only named structs are considered (a non-empty `-type` also skips anonymous structs and struct literals). In diff mode `structalign` exits **1 when any reordering is found** and **0 otherwise**, so it drops into CI as a check; `-inspect` always exits 0. Note the most compact ordering is not always the most efficient — beware false sharing (see `-skip-cache-padded`). structalign ./... # scan every package in the module structalign -diff=side -summary ./... # side-by-side diff plus a total structalign -inspect -type=Config ./pkg # one struct's per-field layout structalign [flags] [packages] packages Go package patterns: ./..., import paths, directories, or single .go files (defaults the go tool understands) -diff value diff style: unified|side|none (default "unified") -format value output format: text|json (default "text") -width int column width per side for -diff=side (default: auto from terminal) -color value colorize: auto|always|never (default "auto") -inspect inspect layout instead of diffing: print each struct as annotated Go source with size/align/padding comments -verbose in -inspect mode, show padding on its own `_` line -tags preserve struct field tags in output (default: strip them) -summary in diff mode, print a one-line summary after the diffs -sort present results largest-first (diff: by bytes saved; inspect: by struct size) -threshold int in diff mode, only show structs that save at least N bytes (default 0; negatives treated as 0) -type string only consider named structs matching these comma-separated glob patterns (e.g. "*Request,Config"); empty means all -exclude string exclude packages whose import path matches this regexp (default "^unsafe$|^builtin$") -generated also analyze generated files (skipped by default) -tests also analyze _test.go files (skipped by default) -skip-cache-padded skip structs with a golang.org/x/sys/cpu.CacheLinePad field -show-nolint show structs even when their type carries a recognized //nolint directive (directives are respected by default) -nolint-linters string //nolint tokens that suppress a finding (default "fieldalignment"; a bare //nolint always counts) -no-rc skip loading .structalignrc files -version print version and exit In the default `-color=auto`, color is emitted only when stdout is a terminal and the [`NO_COLOR`](https://no-color.org) environment variable is unset. `NO_COLOR` (any non-empty value) disables color; an explicit `-color=always` overrides it. ### Configuration `structalign` supports persistent defaults via environment variables and `.structalignrc` files. Precedence (highest wins): 1. **CLI flags** (e.g. `structalign -sort`) 2. **Environment variables**: `STRUCTALIGN_`, e.g. `STRUCTALIGN_SORT=true`. 3. **Local config**: `.structalignrc` in the current directory. 4. **Global config**: `~/.structalignrc`. The configuration files use a simple `key = value` format: # .structalignrc example sort = true threshold = 8 skip-cache-padded = true Keys map directly to flag names. To skip loading configuration files (e.g. in CI), use the `-no-rc` flag. Note that **theme** is not an RC key; set it via the `STRUCTALIGN_THEME` environment variable. #### Configuration Reference | Feature | CLI Flag | Environment Variable | RC Key | Default | |---------|----------|----------------------|--------|---------| | Diff style | `-diff` | `STRUCTALIGN_DIFF` | `diff` | `unified` | | Output format | `-format` | `STRUCTALIGN_FORMAT` | `format` | `text` | | Column width | `-width` | `STRUCTALIGN_WIDTH` | `width` | `0` (auto) | | Color mode | `-color` | `STRUCTALIGN_COLOR` | `color` | `auto` | | Theme palette | — | `STRUCTALIGN_THEME` | — | `default` | | Inspect mode | `-inspect` | `STRUCTALIGN_INSPECT` | `inspect` | `false` | | Verbose inspect | `-verbose` | `STRUCTALIGN_VERBOSE` | `verbose` | `false` | | Keep tags | `-tags` | `STRUCTALIGN_TAGS` | `tags` | `false` | | Show summary | `-summary` | `STRUCTALIGN_SUMMARY` | `summary` | `false` | | Largest-first sort | `-sort` | `STRUCTALIGN_SORT` | `sort` | `false` | | Min bytes saved | `-threshold` | `STRUCTALIGN_THRESHOLD` | `threshold` | `0` | | Type filter | `-type` | `STRUCTALIGN_TYPE` | `type` | (empty) | | Package exclude | `-exclude` | `STRUCTALIGN_EXCLUDE` | `exclude` | `^unsafe$\|^builtin$` | | Include generated | `-generated` | `STRUCTALIGN_GENERATED` | `generated` | `false` | | Include tests | `-tests` | `STRUCTALIGN_TESTS` | `tests` | `false` | | Skip cache padded | `-skip-cache-padded` | `STRUCTALIGN_SKIP_CACHE_PADDED` | `skip-cache-padded` | `false` | | Show //nolint | `-show-nolint` | `STRUCTALIGN_SHOW_NOLINT` | `show-nolint` | `false` | | Nolint linters | `-nolint-linters` | `STRUCTALIGN_NOLINT_LINTERS` | `nolint-linters` | `fieldalignment` | The palette can be switched with the `STRUCTALIGN_THEME` environment variable — ... Applied fuzzy match at line 147. `default` (the standard colors), `cga` (the iconic cyan/magenta/white CGA palette, with a reverse-video header bar), or `green` / `amber` (single-hue phosphor-monitor emulations). It only affects *which* colors are used when color is on; it does not turn color on by itself. An unknown value warns and falls back to `default`. ## Modes ### Diff (default) Unified diff: $ structalign -type=Mixed ./_example _example/types.go:6:12: Mixed: struct of size 24 could be 16, saving 8 bytes (33.33% smaller) type Mixed struct { + B int64 A bool - B int64 C bool } Side-by-side: $ structalign -diff=side -width=28 -type=Mixed ./_example _example/types.go:6:12: Mixed: struct of size 24 could be 16, saving 8 bytes (33.33% smaller) current │ proposed ─────────────────────────────┼───────────────────────────── type Mixed struct { │ type Mixed struct { │ B int64 A bool │ A bool B int64 │ C bool │ C bool } │ } Print the reordered struct only (no diff): `structalign -diff=none ./_example`. With `-summary`, a one-line aggregate is appended after the diffs (counting only the structs shown, and the bytes their reorderings would save): $ structalign -summary ./_example ... (diffs above) ... Summary: 5 structs affected, 56 bytes saved total ### Inspect layout `-inspect` skips the alignment analyzer entirely and prints each (filtered) named struct as annotated Go source: the declaration with per-field `// size: N, align: M` comments, column-aligned, plus a size/align/padding summary on the opening line. Padding is folded onto the field comment by default: $ structalign -inspect -type=Mixed ./_example type Mixed struct { // size: 24, align: 8, padding: 14 A bool // size: 1, align: 1, padding: 7 B int64 // size: 8, align: 8 C bool // size: 1, align: 1, padding: 7 } With `-verbose`, padding moves onto its own `_` line: $ structalign -inspect -verbose -type=Mixed ./_example type Mixed struct { // size: 24, align: 8, padding: 14 A bool // size: 1, align: 1 _ // 7 byte padding B int64 // size: 8, align: 8 C bool // size: 1, align: 1 _ // 7 byte padding } The layout comes from the same `go/types` sizing the diff modes use (`types.Sizes.Offsetsof` / `Sizeof` / `Alignof`), driven by the toolchain's target sizes (your host `GOOS`/`GOARCH` by default). This is similar to [`honnef.co/go/tools/cmd/structlayout`][sl], but stays inside this one tool and honors the same `-type` filter. #### Inspecting generic types A generic struct has **no single layout** — `type Box[T any] struct{ … }` is laid out differently for every type argument (`Box[bool]` and `Box[[64]byte]` share nothing), so there is no concrete type to measure. Inspect therefore shows a **best-effort approximation**: each type parameter is measured as a representative type — its constraint's core type (e.g. `~int` → `int`), or `interface{}` when the constraint is unbounded (`any`, `comparable`, unions). Fields keep their source form (`Value T`, not `Value any`), and every field whose size depends on a type parameter is annotated with the assumption it was measured under (`-- assume T=any`). The output is also prefixed with a disclaimer. Treat the numbers as indicative only; the real layout depends on how the type is instantiated. $ structalign -inspect -type=Generic ./_example // generic type — layout assumes T=any; the real layout depends on the type argument(s) type Generic[T] struct { // size: 32, align: 8, padding: 11 Flag bool // size: 1, align: 1, padding: 7 Value T // size: 16, align: 8 -- assume T=any Count uint32 // size: 4, align: 4, padding: 4 } A field can depend on a type parameter indirectly — through a composite or a nested generic — and the marker follows it: `map[K]V` reports `-- assume K=any, V=any`, and `Inner[V]` reports `-- assume V=any`. #### Inspecting types you don't own structalign resolves its package arguments through `go/packages`, so you can point `-inspect` (and the diff modes) at types you didn't write — as long as the package is reachable from the **current directory's `go.mod`**. Standard-library structs work out of the box — give the import path and a `-type` filter: $ structalign -inspect -type=Time time type Time struct { // size: 24, align: 8, padding: 0 wall uint64 // size: 8, align: 8 ext int64 // size: 8, align: 8 loc *Location // size: 8, align: 8 } Dependencies already in your `go.mod` resolve the same way: $ structalign -inspect -type=Group golang.org/x/sync/errgroup type Group struct { // size: 64, align: 8, padding: 4 cancel func(error) // size: 8, align: 8 wg sync.WaitGroup // size: 16, align: 8 sem chan token // size: 8, align: 8 errOnce sync.Once // size: 12, align: 4, padding: 4 err error // size: 16, align: 8 } Any other library must be *required* by the module you run in — resolution is against the current `go.mod`, **not** arbitrary packages sitting in `$GOPATH` or the module cache. A package the module doesn't require fails with `no required module provides package …`. The quickest way to inspect an arbitrary library is a throwaway module: mkdir /tmp/inspect && cd /tmp/inspect go mod init scratch go get github.com/rs/zerolog structalign -inspect -type=Logger github.com/rs/zerolog Built-in **scalar** types (`int`, `bool`, `string`, …) can't be inspected: inspect prints a *struct field layout*, and scalars have no fields. (The `builtin` pseudo-package is in the default `-exclude` for the same reason.) To see a scalar's size, inspect a struct that contains it — a `string` field shows `size: 16` on a 64-bit target. ### JSON output `-format=json` (or `STRUCTALIGN_FORMAT=json`, or `format = json` in `.structalignrc`) emits a single structured document instead of the rendered text, for both diff and inspect modes. It carries the same data the text renderers show — findings include `original` / `proposed`, `oldSize` / `newSize` / `bytesSaved`; inspect layouts include per-field `offset` / `size` / `align` / `padding` and the generic `assume` notes. $ structalign -format=json -type=Mixed ./_example { "version": "...", "mode": "diff", "findings": [ ... ], "summary": { "structsAffected": 1, "bytesSaved": 8 } } Two things differ from text mode by design: - **The diff document always includes the `summary` block** (so a machine consumer always gets the totals). `-summary` only governs the text renderer's trailing summary line. - **The presentation flags don't apply.** `-diff`, `-summary`, `-verbose`, `-color`, and `-width` shape the *text* output only; in JSON mode they are ignored, since the consumer renders from the structured fields itself. `-tags` still applies — it gates whether the inspect document's per-field `tag` field is emitted (see [Field tags](#field-tags)). ### Filtering by type name `-type` takes a comma-separated list of glob patterns (`path.Match` syntax: `*`, `?`, `[...]`) matched against the *declared* name of each struct type. Anonymous structs and struct literals are never matched by a non-empty filter. It applies to every mode: structalign -type='*Request' ./... # only structs ending in Request structalign -type='Record,Config' ./pkg # exact names structalign -inspect -type='*ID*' ./pkg # inspect just ID-related structs ### Scanning scope By default, structalign analyzes the regular, hand-written source of each package. A few flags adjust what's in scope: structalign -generated ./... # include generated files (skipped by default) structalign -tests ./... # include _test.go files (skipped by default) structalign -exclude='/internal/' ./... # drop packages whose import path matches the regexp structalign -skip-cache-padded ./... # skip structs guarded by cpu.CacheLinePad - **Generated files** (`// Code generated … DO NOT EDIT.`) are skipped by default — you usually can't hand-edit them, so a reorder suggestion would be noise. - **`_test.go` files** are skipped by default; `-tests` includes them. - **`-exclude`** takes a regexp matched against the *import path* (default `^unsafe$|^builtin$`); it complements `-type`, which matches struct names. - **`-skip-cache-padded`** leaves structs with a [`cpu.CacheLinePad`](https://pkg.go.dev/golang.org/x/sys/cpu#CacheLinePad) field alone, since reordering would move the pad and defeat its false-sharing guard. - **`//nolint` directives are respected by default** (diff mode): a struct whose type declaration carries a recognized `//nolint` — `//nolint:fieldalignment` or a bare `//nolint` — is suppressed, matching golangci-lint. `-nolint-linters` customizes which named tokens count (default `fieldalignment`; e.g. `-nolint-linters=fieldalignment,betteralign`); a bare `//nolint` always counts. `-show-nolint` reveals suppressed structs (audit mode). Inspect mode ignores these directives. ### Field tags By default the tool **strips struct field tags** from all output, so the focus stays on field order and layout rather than tag text. This matters most in diff mode: reordering changes column widths, which makes `gofmt` re-align tags, and those re-spacing changes would otherwise show up as diff noise unrelated to the actual reorder. Stripping tags from both sides removes that distraction. Pass `-tags` to keep tags. In diff mode they stay bound to their fields as the fields move; in inspect mode they are appended to each field declaration (with comments still column-aligned): $ structalign -inspect -tags -type=Tagged ./_example type Tagged struct { // size: 48, align: 8, padding: 18 Flag bool `json:"flag"` // size: 1, align: 1, padding: 7 ID string `json:"id" db:"id"` // size: 16, align: 8 Count uint32 `json:"count"` // size: 4, align: 4, padding: 4 Ptr *uint64 // size: 8, align: 8 Enabled bool `json:"enabled"` // size: 1, align: 1, padding: 7 } Tags never affect the layout numbers (size/offset/alignment are independent of tags), so stripping them changes only the display, never the analysis. The same flag governs JSON output: with `-format=json`, the inspect document's `tag` field is emitted only when `-tags` (or `STRUCTALIGN_TAGS=true`, or `tags = true` in `.structalignrc`) is in effect. ## How it works `structalign` does **not** reimplement the alignment algorithm. It runs the **unmodified** `fieldalignment.Analyzer`, intercepts the `analysis.SuggestedFix` it already produces (a single `TextEdit` replacing the whole struct node with the optimally-ordered, gofmt'd version), and diffs that against your original source. Because all the alignment logic — including the GC pointer-bytes optimization and size calculations — comes straight from upstream, results match `fieldalignment` exactly. Only the *presentation* is new. ## Building from source Requires **Go 1.25+** (the floor set by `golang.org/x/tools`). The repo uses [Task](https://taskfile.dev) ([`golangci-lint`](https://golangci-lint.run) handles both linting and formatting); the `Makefile` just delegates to `task`. git clone https://github.com/peczenyj/structalign cd structalign task build # -> ./structalign (or: go build -o structalign .) task ci # lint, build, test, and a smoke test against ./_example task --list # list all tasks `main.go` (at the module root) is a thin entrypoint; the implementation lives in small packages under `pkg/common` (contracts) and `internal/` (loader, align, layout, ui, app, …). `_example/` holds sample structs for manual testing — the leading underscore keeps the Go tool from treating it as a package, so it stays out of `go build ./...` and friends. ## Caveats inherited from fieldalignment - The most compact order is not always the most efficient — packing fields tightly can occasionally induce false sharing between goroutines. For deliberately cache-line-padded structs, use `-skip-cache-padded`. - Reordering can hurt logical grouping/readability; treat the output as advice, most valuable for hot, frequently-allocated structs. - Sizes are computed for the toolchain's target (your host `GOOS`/`GOARCH` by default). To analyze another target, set them in the environment, e.g. `GOARCH=386 structalign ./...`. - For **generic** structs both modes work from the type parameters' assumed (constraint) sizes, so the result may not match a particular instantiation — diff may suggest a non-optimal order, and inspect's numbers are approximate (it prints a disclaimer; see [Inspecting generic types](#inspecting-generic-types)). ## Design notes ### Pipeline 1. Load the target packages with `golang.org/x/tools/go/packages` (mode including syntax, types, type info, and `TypesSizes`). This resolves `./...`, import paths, directories, and single files the way the `go` tool does, and supplies the analyzer's size math from the real build target. 2. Satisfy the analyzer's only dependency — the `inspect` pass — by building an `inspector.New(pkg.Syntax)` and placing it in `Pass.ResultOf`. 3. Provide a custom `Pass.Report` that captures each diagnostic's `NewText` (the proposed struct) and reads the original source slice between `Pos` and `End`. 4. Diff the two with `github.com/aymanbagabas/go-udiff` (a maintained standalone port of the Myers diff packages gopls uses, via `udiff.Lines`) and render the result as a unified or side-by-side diff, or just print the reordered struct. ### Dependencies and the internal-package rule This tool lives in its own standalone module (`github.com/peczenyj/structalign`) and pulls two dependencies as ordinary `go get`-able modules: - `golang.org/x/tools` — for the public `.../passes/fieldalignment` analyzer. - `github.com/aymanbagabas/go-udiff` — for line diffing. Go's internal-package rule says a package may import `/internal/...` only if the **importing package's own path** is rooted at `/`. That is why diffing uses `go-udiff` rather than x/tools' own diff package: - `fieldalignment` imports `golang.org/x/tools/internal/astutil` — fine, because the importer is itself under `golang.org/x/tools/`. This tool only touches `fieldalignment`'s public API, so importing the analyzer from any module works. - `golang.org/x/tools/internal/diff`, by contrast, **cannot** be imported from `github.com/peczenyj/structalign` (not under `golang.org/x/tools/`), so the compiler rejects it. `go-udiff` is a public port of the same gopls diff code, so the results are equivalent. ## Easter eggs A few hidden flags are intentionally kept out of `-help` and the flag table above: **`-cga`, `-green`, `-amber`** — shortcuts for the retro-theme palettes otherwise selected with `STRUCTALIGN_THEME=…`. Pick one per invocation; the egg flag wins over the environment variable. structalign -cga ./... # cyan/magenta/white CGA palette structalign -green -inspect ./_example # single-hue green-phosphor look structalign -amber -diff=side ./... # amber-phosphor side-by-side diff They are stripped before flag parsing, so they never trip *"flag provided but not defined"* when combined with normal flags. ## Changelog task changelog # regenerate CHANGELOG.md task changelog:unreleased # preview pending entries task release TAG=v0.1.0 # stamp the changelog for a release ## Prior work `structalign` builds upon — and is indebted to — the following prior work: - [**fieldalignment**](https://github.com/golang/tools/tree/master/go/analysis/passes/fieldalignment) by the Go Authors — the upstream analyzer structalign wraps; all the alignment math comes straight from it. - [**betteralign**](https://github.com/dkorunic/betteralign) by Dinko Korunić — a maintained successor to `fieldalignment` that also applies fixes; structalign recognizes its `//nolint:betteralign` directives via `-nolint-linters`. - [**maligned**](https://github.com/mdempsky/maligned) by Matthew Dempsky — the original struct field-alignment detector, since superseded by `fieldalignment`. - [**structslop**](https://github.com/orijtech/structslop) by orijtech — suggests struct field rearrangements to reduce memory footprint. ## License [MIT](LICENSE) © Tiago Peczenyj
标签:EVTX分析