C11 alignas vs. clang -Wcast-align
Asked Answered
M

1

22

So I have the following minimized C11 code that defines a struct containing a uint16_t (which means the struct it should be aligned to 2 bytes) and I want to cast a char buffer to a pointer to that struct.

With warnings turned all up, clang rightly complained that the alignment requirements of the struct are not met. So I added a C11 alignas specifier to the buffer to make sure the buffer is sufficiently aligned, but that didn't shut up clang.

My question is: am I doing something wrong with alignas? Or is it just that the -Wcast-align diagnostic is only looking at the type of the arguments and not also at the manually specified alignment? (I realize I can just cast to void* to silence the diagnostic, but since this piece of code is supposed to be portable, I don't want to side-step the diagnostic unless I am certain it is a false positive.)

#include <stdint.h>
#include <stdalign.h>

struct foo {
    uint16_t field1;
};


int main(void) {
    alignas(struct foo) char buffer[122] = {0};
    struct foo *foo = (struct foo*)buffer;
    return foo->field1;
}

Compiler options and error message:

$ clang -ggdb -O3 foo.c -Weverything -Werror -Wno-c++98-compat -Wno-c11-extensions
foo.c:11:23: error: cast from 'char *' to 'struct foo *' increases required alignment from 1 to 2 [-Werror,-Wcast-align]
    struct foo *foo = (struct foo*)buffer;
                      ^~~~~~~~~~~~~~~~~~~~~~~~~

Compiler version:

$ clang -v
clang version 3.5.1 (tags/RELEASE_351/final)
Target: x86_64-pc-linux-gnu
Thread model: posix
Selected GCC installation: /usr/lib/gcc/x86_64-pc-linux-gnu/4.8.4

Update: There is no warning when I move the buffer and its alignment into a struct. I interpret this as a hint that clang does indeed only look at the types for this warning.

#include <stdint.h>
#include <stdalign.h>

struct foo {
    uint16_t field1;
};

struct bar {
    alignas(struct foo) char buffer[122];
};


int main(void) {
    struct bar bar = {{0}};
    struct foo *foo = (struct foo*)&bar;
    return foo->field1;
}
Metaplasia answered 14/2, 2015 at 14:7 Comment(6)
Or is it just that the -Wcast-align diagnostic is only looking at the type of the arguments and not also at the manually specified alignment that's also my guess.Forcible
Isn't this - https://mcmap.net/q/189060/-where-can-i-use-alignas-in-c-11 relevant?Metamorphic
@Kamiccolo: clang and clang++ on my machine don't compile any of the "correct" versions in that post. The error message are "'alignas' attribute only applies to variables, data members and tag types" and "expected ';' at end of declaration".Metaplasia
Can you use a c++ style cast and see if you run into the same problem?Thor
@bentank: I don't really know what I'm doing with c++ casts. The only one that seems to compile is a reinterpret_cast, which doesn't seem to warn about wrong alignment, ever.Metaplasia
reinterpret_cast was what I was thinking. I did not look through the source of clang to see how that is handled.Thor
T
2

From clang source, in SemaChecking.cpp:~7862, it seems they are only looking at types like you mention:

  CharUnits SrcAlign = Context.getTypeAlignInChars(SrcPointee);
  if (SrcAlign >= DestAlign) return;

  // else warn...

It looks to me like clang is preparing for a c-style cast which in turn will check for cast alignment.

void CastOperation::CheckCStyleCast()
    -> Kind = CastKind Sema::PrepareScalarCast(...);
        -> if (Kind == CK_BitCast)
               checkCastAlign();

void checkCastAlign() {
  Self.CheckCastAlign(SrcExpr.get(), DestType, OpRange);
}

Here is the method with a little more context:

/// CheckCastAlign - Implements -Wcast-align, which warns when a
/// pointer cast increases the alignment requirements.
void Sema::CheckCastAlign(Expr *Op, QualType T, SourceRange TRange) {
  // This is actually a lot of work to potentially be doing on every
  // cast; don't do it if we're ignoring -Wcast_align (as is the default).
  if (getDiagnostics().isIgnored(diag::warn_cast_align, TRange.getBegin()))
    return;

  // Ignore dependent types.
  if (T->isDependentType() || Op->getType()->isDependentType())
    return;

  // Require that the destination be a pointer type.
  const PointerType *DestPtr = T->getAs<PointerType>();
  if (!DestPtr) return;

  // If the destination has alignment 1, we're done.
  QualType DestPointee = DestPtr->getPointeeType();
  if (DestPointee->isIncompleteType()) return;
  CharUnits DestAlign = Context.getTypeAlignInChars(DestPointee);
  if (DestAlign.isOne()) return;

  // Require that the source be a pointer type.
  const PointerType *SrcPtr = Op->getType()->getAs<PointerType>();
  if (!SrcPtr) return;
  QualType SrcPointee = SrcPtr->getPointeeType();

  // Whitelist casts from cv void*.  We already implicitly
  // whitelisted casts to cv void*, since they have alignment 1.
  // Also whitelist casts involving incomplete types, which implicitly
  // includes 'void'.
  if (SrcPointee->isIncompleteType()) return;

  CharUnits SrcAlign = Context.getTypeAlignInChars(SrcPointee);
  if (SrcAlign >= DestAlign) return;

  Diag(TRange.getBegin(), diag::warn_cast_align)
    << Op->getType() << T
    << static_cast<unsigned>(SrcAlign.getQuantity())
    << static_cast<unsigned>(DestAlign.getQuantity())
    << TRange << Op->getSourceRange();
}

static const Type* getElementType(const Expr *BaseExpr) {
  const Type* EltType = BaseExpr->getType().getTypePtr();
  if (EltType->isAnyPointerType())
    return EltType->getPointeeType().getTypePtr();
  else if (EltType->isArrayType())
    return EltType->getBaseElementTypeUnsafe();
  return EltType;
}
Thor answered 2/5, 2015 at 6:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.