6

I have a C++ class which has a private unused char[] strictly to add padding to the class to prevent false sharing when the class is used in a shared array. My question is 2-fold:

  1. Can this data member be optimized out by the compiler in some circumstances?

  2. How can I silence the private field * not used warnings when I compile with -Wall? Preferably, without explicitly silencing the warning as I still want to catch instances of this issue elsewhere.

I wrote a little test to check for my compiler, and it seems that the member isn't removed, but I want to know if the standards allow this sort of optimization.

padding.cc

#include <iostream>

class A {
 public:
  int a_ {0};

 private:
  char padding_[64];
};

int main() {
  std::cout << sizeof(A) << std::endl;
  return 0;
}

compilation

$ clang++ --version
clang version 3.3 (tags/RELEASE_33/final)
Target: x86_64-unknown-linux-gnu
Thread model: posix

$ clang++ -std=c++11 -O3 -Wall padding.cc
padding.cc:8:8: warning: private field 'padding_' is not used [-Wunused-private-field]
  char padding_[64];
       ^
1 warning generated.

$ ./a.out 
68
Cookyt
  • 1,607
  • 1
  • 12
  • 12
  • Data cannot be removed from the structure. Compilers are required to preserve all the data members and order they appear. – bolov Apr 19 '14 at 23:04
  • After reading the answers, I'm reasonably confident that it won't be removed, but as to the order of data members: order is not guaranteed between different access specifiers (public, private, etc.) (http://stackoverflow.com/questions/916600/can-a-c-compiler-re-order-elements-in-a-struct) – Cookyt Apr 20 '14 at 01:33
  • `alignas(64) int a_{0};` would be a better choice. It's probably less optimal for your object's size to be 68 bytes than 64. – Peter Cordes May 01 '23 at 19:21

4 Answers4

3

I don't know about the compiler optimizations, but you can get rid of the warnings in two ways: Either use pragmas:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-private-field"
class A{
//...
};
#pragma clang diagnostic pop

or, which is probably better suited for you, include a fake friend function in your class:

class A{
friend void i_do_not_exist();
//... 
};

In that way, the compiler cannot know if the field is used or not. Therefore, it does not complain and will definitely not throw anything out. This can lead to safety issues if the i_do_not_exist() function is ever defined anywhere, as that function is given direct access to the private members of the class.

A third solution is to define a dummy function which access the padding_ member:

class A {
 private:
  void ignore_padding__() { padding_[0] = 0; }
  //... 
};
Cookyt
  • 1,607
  • 1
  • 12
  • 12
Roberto
  • 3,003
  • 1
  • 18
  • 25
  • That friend declaration is a neat trick as I want to remain as compiler-agnostic as possible. Are there any pitfalls to using it that I should be aware of? – Cookyt Apr 20 '14 at 01:23
  • 1
    I'm no guru, but besides the fact that it looks somewhat ugly in your interface and somebody could actually access the `padding_` field from the outside, I can't think of anything. Of course it becomes unsafe as soon as you add meaningful private data to `A`. In terms of performance or memory footprint there should be no drawback. – Roberto Apr 20 '14 at 01:34
  • Do you mean that it's unsafe to add other private data because now someone could declare a function with the same name which can access the said private data? – Cookyt Apr 20 '14 at 01:36
  • Exactly. They would be free to do as they please. Maybe an even better way is to have a member `A::dummy(){padding_[0]=0;}` – Roberto Apr 20 '14 at 01:37
  • Thanks, that seems like the best way to do it, really. – Cookyt Apr 20 '14 at 02:02
1

The compiler can perform any change that can't be detected by a conforming program. So the answer is yes. But a compiler that makes changes that make your code worse is a lousy compiler. Odds are, you aren't using a lousy compiler.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
1

I'm pretty sure compilers aren't allowed to reorder or remove data members, so the .h files are self-documenting for anyone writing an API that accepts such a struct. They're only allowed to use simple and well-defined padding rules so developers can easily infer the offsets just from reading the code.

That said, why are you making assumptions on the cache size and the likelihood of false sharing? The cache size should be the compiler's responsibility, and I suspect the real issue is trying to share an array between multiple threads. Update the struct locally on each thread and only write out the changes to the shared array at the end.

BonzaiThePenguin
  • 1,413
  • 13
  • 19
  • The code above is just a quick test I wrote. In the real code, the cache line size is configured at compile time. I'm writing lock-free code and changes to the members of the array have to be visible immediately, so it doesn't make sense, in this context, to update locally on the thread and then push to shared memory. – Cookyt Apr 20 '14 at 01:01
0

How can I silence the "private field * not used" warnings when I compile with -Wall?

First, you might used alignas to avoid manual padding (if value is a power of 2):

class alignas(64) A
{
public:
  int a_{0};
};

Demo

It is not the case of your example :-/

So you might use attribute [[maybe_unused]] to silent the warning:

class A
{
public:
  int a_ {0};

private:
  [[maybe_unused]]char padding_[64];
};

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302