4

Is there a guaranteed way to assert that a given raw pointer is aligned to some alignment value?

I looked at pointer's aligned_offset function, but the docs state that it is permissible for it to give false negatives (always return usize::MAX), and that correctness cannot depend on it.

I don't want to fiddle with the alignment at all, I just want to write an assertion that will panic if the pointer is unaligned. My motivation is that when using certain low-level CPU intrinsics passing a pointer not aligned to some boundary causes a CPU error, and I'd much rather get a Rust panic message pointing where the bug causing it is located than a SEGFAULT.

An example assertion (not correct according to aligned_offset docs):

#[repr(align(64))]
struct A64(u8);

#[repr(align(32))]
struct A32(u8);

#[repr(align(8))]
struct A8(u8);

fn main() {
    let a64 = [A64(0)];
    let a32 = [A32(0)];
    let a8 = [A8(0), A8(0)];
    
    println!("Assert for 64 should pass...");
    assert_alignment(&a64);
    println!("Assert for 32 should pass...");
    assert_alignment(&a32);
    
    println!("Assert for 8, one of the following should fail:");
    println!("- full array");
    assert_alignment(&a8);
    println!("- offset by 8");
    assert_alignment(&a8[1..]);
}

fn assert_alignment<T>(a: &[T]) {
    let ptr = a.as_ptr();
    
    assert_eq!(ptr.align_offset(32), 0);
}

Rust playground.

6
  • 3
    I'm not an expert on alignment, but couldn't you just cast the pointer to a usize and take the modulus of the desired byte alignment? Something like (ptr as usize) % alignment == 0? Commented Apr 22, 2022 at 19:49
  • 3
    @JMAA I doubt that it'd be that simple, since the underlying pointer value is architecture specific. 1. This answer: stackoverflow.com/a/57970536/4646738 points to an architecture that doesn't work like that, 2. If it was that easy then I'd expect the standard function to just do that, the "can be incorrect" caveat wouldn't be not needed. Commented Apr 22, 2022 at 21:13
  • Fair point, but do you need to support architectures that don't represent pointers in a way that would work like this? The question you linked suggests that the only way to make a version that worked for every conceivable architecture would be to write architecture-specific code for every special case. Or, you just accept that at least x86, amd64, and aarch64 (AFAIK) all work like this and that's good enough Commented Apr 23, 2022 at 10:00
  • I would expect also the std function version to work properly on all such architectures, so you could just bug an extra check to see if it returns usize::MAX and fail when it does if you're extra paranoid. But really, unless you're targeting the 8086 and looking at >16 bit alignment at the same time, this is probably a non-issue Commented Apr 23, 2022 at 10:05
  • @JMAA I'm trying to be architecture-agnostic as much as I can for my current usage. Although, even if I decide to go with a half-solution, the question of whether this can be done in a general way still nags me. You can demand certain alignment from the memory allocator, so intuitively (for me) there should be some way of asking the allocator to tell you the alignment. Commented Apr 23, 2022 at 11:02

1 Answer 1

2

Just to satisfy my own neuroses, I went and checked the the source of pointer::align_offset.

There's a lot of careful work around edge cases (e.g. const-evaluated it always returns usize::MAX, similarly for a pointer to a zero-sized type, and it panics if alignment is not a power of 2). But the crux of the implementation, for your purposes, is here: it takes (ptr as usize) % alignment == 0 to check if it's aligned.

Edit: This PR is adding a pointer::is_aligned_to function, which is much more readable and also safer and better reviewed than simply (ptr as usize) % alginment == 0 (though the core of it is still that logic).

There's then some more complexity to calculate the exact offset (which may not be possible), but that's not relevant for this question.

Therefore:

assert_eq!(ptr.align_offset(alignment), 0);

should be plenty for your assertion.

Incidentally, this proves that the current rust standard library cannot target anything that does not represent pointers as simple numerical addresses, otherwise this function would not work. In the unlikely situation that the rust standard library is ported to the Intel 8086 or some weird DSP that doesn't represent pointers in the expected way, this function would have to change. But really, do you care for that hypothetical that much?

Sign up to request clarification or add additional context in comments.

6 Comments

And that is the explanation I think. I would guess that on such an esoteric architecture there wouldn't necessarily be a sensible implementation, it would just default to returning usize::MAX always and still be a valid Rust implementation for that architecture.
There is a pending PR for is_aligned() and is_aligned_to(): github.com/rust-lang/rust/pull/95643.
By itself, the fact that std doesn't implement something correctly for some platform doesn't mean Rust completely not supports this platform, only that it is in a tier std is not (completely, at least) supported. In this case there is also a difference from C: The C99 standard states that (§6.3.2.3 Pointers) "Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined. If the result cannot be represented in the integer type, the behavior is undefined. The result need not be in the range of values of any integer type."...
...while the Rust reference states that (doc.rust-lang.org/stable/reference/expressions/…) "Casting from a raw pointer to an integer produces the machine address of the referenced memory. If the integer type is smaller than the pointer type, the address may be truncated; using usize avoids this." So the standard library is correct, because this requires the machine address - I read that as actual address, after segmentation.
This definition is also compatible with the 8086 (although casts will require more operations to adjust the address). Though it may not be what you expect: it will require usize to be 32 bits to match every address. This is also necessary by doc.rust-lang.org/stable/reference/…, "usize and isize have a size big enough to contain every address on the target platform." But it is well-known Rust does not support segmented architectures well, and some recent work (strict provenance) may improve this situation. So those definitions may change.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.