I have a 2D matrix
matrix[m][n];
I know that matrix is a double pointer with type int**. I would like to obtain a double pointer pointing to a submatrix of the original matrix. For example, I want the submatrix to start for cell (1,1). How do I get such a double pointer from the original matrix[m][n]?
I know that matrix is a double pointer with type int**.
No, you don't. Arrays are not pointers. If you declared it as int matrix[m][n];, then the type of the expression matrix is int [m][n]; unless matrix is the operand of the sizeof or unary & operators, it will have its type converted ("decay") to int (*)[n] (pointer to n-element array of int).
The problem is that you can't create arbitrary submatrices by just declaring a pointer of the right type; C and C++ don't provide an easy way to "slice" arrays this way. You can certainly create a pointer of type int (*)[n-1] and assign the value of &matrix[1][1] to it (with an appropriate cast), but it won't do what you want.
EDIT
Now that I have a real keyboard in front of me I can expand on this a little bit.
Let's imagine a 3x3 matrix declared as follows:
int m[3][3] = {{0,1,2},{3,4,5},{6,7,8}};
We normally visualize such a matrix as
+---+---+---+
| 0 | 1 | 2 |
+---+---+---+
| 3 | 4 | 5 |
+---+---+---+
| 6 | 7 | 8 |
+---+---+---+
In C and C++, 2-dimensional arrays are laid out in row-major order1, 2, so the above matrix would be represented in memory as
+---+
m: | 0 | m[0][0]
+---+
| 1 | m[0][1]
+---+
| 2 | m[0][2]
+---+
| 3 | m[1][0]
+---+
| 4 | m[1][1]
+---+
| 5 | m[1][2]
+---+
| 6 | m[2][0]
+---+
| 7 | m[2][1]
+---+
| 8 | m[2][2]
+---+
So suppose you want the 2x2 submatrix starting at m[1][1]:
+---+---+---+
| 0 | 1 | 2 |
+---+---+---+
| 3 | +---+---+
+---+ | 4 | 5 |
| 6 | +---+---+
+---+ | 7 | 8 |
+---+---+
That corresponds to the following array elements:
+---+
m: | 0 | m[0][0]
+---+
| 1 | m[0][1]
+---+
| 2 | m[0][2]
+---+
| 3 | m[1][0]
+---+
+---+
| 4 | m[1][1]
+---+
| 5 | m[1][2]
+---+
+---+
| 6 | m[2][0]
+---+
+---+
| 7 | m[2][1]
+---+
| 8 | m[2][2]
+---+
That's not a contiguous subarray within m, so just declaring a pointer and setting it to &m[1][1] won't do what you really want. You'll need to create a separate matrix object and copy the elements you want to it:
int subm[2][2] = {{m[1][1], m[1][2]}, {m[2][1], m[2][2]}};
You can write a function to grab a 2x2 "slice" of your matrix like so:
void slice2x2( int (*mat)[3], int (*submat)[2], size_t startx, size_t starty )
{
for ( size_t i = 0; i < 2; i++ )
for ( size_t j = 0; j < 2; j++ )
submat[i][j] = mat[startx + i][starty + j];
}
int main( void )
{
int matrix[3][3] = {{0,1,2},{3,4,5},{6,7,8}};
int submat[2][2];
slice2x2( matrix, submat, 1, 1 );
// do something with submat
}
Pre-publication draft of the C 2011 standard, §6.2.5.1, ¶3.
Pre-publication draft of the C++ 2014 standard, §8.3.4, ¶9
A matrix defined as 2D array of constant size:
int matrix [m][n];
is stored as m contiguous blocks of n elements. You can therefore imagine this technically as a flat sequence of m*n elements in memory. You can use pointer arithmetic to find the start of a row, or to find a specific element. But you can't locate a submatrix int that way.
The "double" pointer:
int **pmatrix;
obeys a different logic: it is a pointer to a pointer and works as an array of m pointers pointing at lines of n consecutive elements. So your elements are not necessarily consecutive. You can use pointer arithmetic and indirection to locate the start of a row or a specific item. But again this can't address a submatrix.
Both matrix and pmatrix can be used with 1D or 2D indexing, but the compiler generates different code to address the elements.
For getting sub-matrices you have to make iterations to find the right elements, using vertical and horizontal offsets, but you can't imagine to pass somehow a pointer to the sub-matrix if you don't copy the right elements in a new matrix of target's size.
Related
I am trying to modify the example code available at https://www.boost.org/doc/libs/1_74_0/libs/graph/example/weighted_matching_example.cpp to include floating point edge weights with maximum_weighted_matching algorithm in boost graph library. However, the code seems to be running forever for certain floating point edge weights.
The relevant code modifications are mentioned below.
const int n_vertices = 4;
add_edge(0, 2, EdgeProperty(0.5), g);
add_edge(0, 3, EdgeProperty(0.1), g);
add_edge(1, 2, EdgeProperty(0.4), g);
add_edge(1, 3, EdgeProperty(0.1), g);
I am new to cpp boost library. Can someone please help me understand why the above modifications doesn't work whereas the algorithm works fine for interger edge weights (and for some floating point edge weights as well).
The original graph looks like
+----+ 5 +----+ 1 +----+ 6
| 1 | ---- | 2 | ---- | 6 |--------------------+
+----+ +----+ +----+ |
| | |
+-----------------------------+-----------------------+ |
| | |
| +----+ 1 +----+ 7 +----+ 5 +----+ 4 +----+ 6 +----+ 7 +----+
| | 0 | ---- | 4 | ---- | 5 | ---- | 9 | ---- | 14 | ---- | 15 | ---- | 10 |
| +----+ +----+ +----+ +----+ +----+ +----+ +----+
| | |
| | 2 |
| | |
| 5 +----+ +----+ 4 +----+ 2 +----+ |
| | 3 | | 8 | ---- | 13 | ---- | 12 | |
| +----+ +----+ +----+ +----+ |
| | 4 |
| | 4 +-----------------------------------------------------------+
| | |
| +----+ 5 +----+ 6 +----+ 5 +----+
+-- | 7 | ---- | 11 | ---- | 16 | ---- | 17 |
+----+ +----+ +----+ +----+
Your graph on the other hand looks like
0.1
+--------------------------------------+
| |
+---+ 0.5 +---+ 0.4 +---+ 0.1 +---+
| 0 | ------ | 2 | ------ | 1 | ------ | 3 |
+---+ +---+ +---+ +---+
I note that the bruteforce method works: Live On Coliru
====== brute_force_maximum_weighted_matching ======
Found a matching:
Matching size is 2, total weight is 0.6
The matching is:
{0, 2}
{1, 3}
Indeed as you mentioned, changing the weights to integers did allow it to work:
add_edge(0, 2, EdgeProperty(/*0.*/5), g);
add_edge(0, 3, EdgeProperty(/*0.*/1), g);
add_edge(1, 2, EdgeProperty(/*0.*/4), g);
add_edge(1, 3, EdgeProperty(/*0.*/1), g);
Live On Coliru printing:
In the following graph:
0.1
+--------------------------------------+
| |
+---+ 0.5 +---+ 0.4 +---+ 0.1 +---+
| 0 | ------ | 2 | ------ | 1 | ------ | 3 |
+---+ +---+ +---+ +---+
====== maximum_weighted_matching ======
Found a matching:
Matching size is 2, total weight is 6
The matching is:
{0, 2}
{1, 3}
====== brute_force_maximum_weighted_matching ======
Found a matching:
Matching size is 2, total weight is 6
The matching is:
{0, 2}
{1, 3}
Why, Though
The $1m question.
I noticed:
the weight is float (tried double)
there's a declared edge_index property that seems unused. I tried
initializing to unique numbers [0..4) for the edges
dropping the property
None of this made any difference.
Then I suspected floating point accuracy issues. So I started scaling the weights by factors of 10 (nEx for X = 1, 2, 3, ... so that the total weights would be 6, 60, 600, ...). E.g.
add_edge(0, 2, EdgeProperty(5e5), g);
add_edge(0, 3, EdgeProperty(1e5), g);
add_edge(1, 2, EdgeProperty(4e5), g);
add_edge(1, 3, EdgeProperty(1e5), g);
In this approach X = -1 is identical to your question weights
This started failing at X = 10, again running indefinitely. At that point I selected double instead of float again, and lo and behold: Live On Coliru:
====== maximum_weighted_matching ======
Found a matching:
Matching size is 2, total weight is 6e+10
The matching is:
{0, 2}
{1, 3}
So... Floating Point Strikes Again?
Before jumping to conclusions, I read the documentation on maximum_weighted_matching. First off
Both maximum_weighted_matching and brute_force_maximum_weighted_matching find a maximum weighted matching in any undirected graph.
This is good, because I was wondering since your graph doesn't "look like" the examples used.
The maximum weighted matching problem was solved by Edmonds in [74]. The implementation of maximum_weighted_matching followed Chapter 6, Section 10 of [20] and was written in a consistent style with edmonds_maximum_cardinality_matching because of their algorithmic similarity. In addition, a brute-force verifier brute_force_maximum_weighted_matching simply searches all possible matchings in any graph and selects one with the maximum weight sum.
Ah. This increases the likelihood that there is an implementation specific bug/undocumented limitation the edmonds_maximum_cardinality_matching implementation, which is not present in the brute_force_maximum_weighted_matching variant.
WORKAROUND #1: Use the brute_force_maximum_weighted_matching algorithm instead
Now since
For maximum_weighted_matching, the management of blossoms is much more involved than in the case of max_cardinality_matching
I thought to also apply max_cardinality_matching itself:
//maximum_weighted_matching(g, &mate[0]);
boost::edmonds_maximum_cardinality_matching(g, mate.data());
Yes, that works without a hitch for weights that hang up maximum_weighted_matching (obviously, since the weights aren't used). So far, so good.
More Docs...
Why is a verification algorithm needed? Edmonds' algorithm is fairly complex, and it's nearly impossible for a human without a few days of spare time to figure out if the matching produced by edmonds_matching on a graph with, say, 100 vertices and 500 edges is indeed a maximum cardinality matching
Oh. Wow. This doesn't immediately inspire confidence.
But nothing in the docs really gives me any indication why it could hang/suffer extreme worst case behaviour on certain weights.
Since floating point accuracy is at fault/involved, let me present secondary workardounds:
Workaround #2: Use long double
Workaround #2: Use decimal floats
Workaround #2: Use long double
Annoyingly, this Just Works™
Since this kind of "guess-work" solution feels bad to me, let's be a little bit more methodical:
Workaround #3: Use decimal floats
Boost Multiprecision has our backs. But just
using Weight = boost::multiprecision::cpp_dec_float_50;
doesn't cut it, because BGL uses std::min to get the minimum of two expressions, and due to expression templates the template argument cannot be deduced¹.
So short of fixing that¹ lets disable expression templates:
using Weight = // boost::multiprecision::cpp_dec_float_50;
boost::multiprecision::number<
boost::multiprecision::cpp_dec_float<50>,
boost::multiprecision::et_off >;
This also does the trick: Live On Coliru.
SUMMARY
These are three workarounds. I suggest workaround #1 because it is reliable. It might not suit your performance needs, however.
In that case I'd consider Workaround #3 while also reporting the current test case as a bug with the library developers.
Listing
Anti-bitrot listing of the Workaround #3:
#include <boost/graph/adjacency_list.hpp>
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <boost/graph/maximum_weighted_matching.hpp>
#include <iostream>
#include <string>
#include <vector>
using Weight = // boost::multiprecision::cpp_dec_float_50;
boost::multiprecision::number<
boost::multiprecision::cpp_dec_float<50>,
boost::multiprecision::et_off >;
using EdgeProperty =
boost::property<boost::edge_weight_t, Weight>;
using my_graph =
boost::adjacency_list<
boost::vecS,
boost::vecS,
boost::undirectedS,
boost::no_property,
EdgeProperty>;
using V = boost::graph_traits<my_graph>::vertex_descriptor;
using E = boost::graph_traits<my_graph>::edge_descriptor;
static auto report(my_graph const& g, std::vector<V> const& mate) {
auto sum = matching_weight_sum(g, &mate[0]);
std::cout << "Found a matching:" << std::endl;
std::cout << "Matching size is " << matching_size(g, &mate[0])
<< ", total weight is " << sum
<< std::endl;
std::cout << std::endl;
std::cout << "The matching is:" << std::endl;
for (V v : boost::make_iterator_range(vertices(g))) {
if (mate[v] != g.null_vertex() && v < mate[v]) {
std::cout << "{" << v << ", " << mate[v] << "}" << std::endl;
}
}
std::cout << std::endl;
return sum;
}
int main() {
// vertices can be refered by integers because my_graph use vector to store
// them
my_graph g(4);
add_edge(0, 2, EdgeProperty(5e-1), g);
add_edge(0, 3, EdgeProperty(1e-1), g);
add_edge(1, 2, EdgeProperty(4e-1), g);
add_edge(1, 3, EdgeProperty(1e-1), g);
// print the ascii graph into terminal (better to use fixed-width font)
std::cout << R"(In the following graph:
0.1
+--------------------------------------+
| |
+---+ 0.5 +---+ 0.4 +---+ 0.1 +---+
| 0 | ------ | 2 | ------ | 1 | ------ | 3 |
+---+ +---+ +---+ +---+
)" << std::endl << std::endl;
Weight sum1 = 0, sum2 = 0;
if (1) {
std::cout << "====== maximum_weighted_matching ======\n";
std::vector<V> mate(num_vertices(g));
maximum_weighted_matching(g, &mate[0]);
sum1 = report(g, mate);
}
// now we check the correctness by compare the weight sum to a brute-force
// matching result note that two matchings may be different because of
// multiple optimal solutions
if (1) {
std::cout << "====== brute_force_maximum_weighted_matching ======\n";
std::vector<V> mate(num_vertices(g));
brute_force_maximum_weighted_matching(g, &mate[0]);
sum2 = report(g, mate);
}
assert(sum1 == sum2);
}
¹ this could easily be fixed in the implementation as well:
//delta3 = std::min(delta3, gamma[*vi] / 2);
delta3 = std::min<edge_property_t>(delta3, gamma[*vi] / 2); // SEHE WAS HERE
//delta2 = std::min(delta2, (*bi)->dual_var / 2);
delta2 = std::min<edge_property_t>(delta2, (*bi)->dual_var / 2); // SEHE WAS HERE
I might makemade a pull request for this one
I have two question about the following code, in particular the int sum(const int *begin, const int *end) function. The thing that I don't understand is why is it that we assign p as a pointer to an immutable constant i.e. begin. But then we also have ++p in the for loop inside sum()? Why is it that it is ++p but not ++*p? And why is it that is is p!=end but not *p!= end?
I was reading that: "In const int *p, *p (content pointed-to) is constant, but p is not constant."
I don't quite understand the difference between the usages of *p and p in this function.
My second question is: What is the reason of declaring const in: const int *p = begin in the for loop of int sum(...)? Is it because in the signature of int sum(...), there is this const being declared for: const int *p = begin ? I.e. is it because begin is being declared as something that is immutable - so that's why in the for loop, we have to declare begin is an immutable constant pointed to by the pointer *p?
/* Function to compute the sum of a range of an array (SumArrayRange.cpp) */
#include <iostream>
using namespace std;
// Function prototype
int sum(const int *begin, const int *end);
// Test Driver
int main() {
int a[] = {8, 4, 5, 3, 2, 1, 4, 8};
cout << sum(a, a+8) << endl; // a[0] to a[7]
cout << sum(a+2, a+5) << endl; // a[2] to a[4]
cout << sum(&a[2], &a[5]) << endl; // a[2] to a[4]
}
// Function definition
// Return the sum of the given array of the range from
// begin to end, exclude end.
int sum(const int *begin, const int *end) {
int sum = 0;
for (const int *p = begin; p != end; ++p) {
sum += *p;
}
return sum;
}
As a reminder, the table of const and pointers:
int * p -- Pointer to a mutable (read/write) location. Pointer and target data may be modified.
int const * p -- Pointer to read only location. The data at the location constant, read-only.
int * const p -- Constant pointer to read/write location. Pointer can't be changed, but data at location may be changed.
int const * const p -- Constant pointer to constant data. Neither pointer nor data may be changed.
In the declaration:
int sum(const int *begin, const int *end);
The pointers are mutable pointers to constant data. The pointers may be modified, but they point to constant (read-only) data.
Edit 1: Incrementing a pointer
Let's assign a pointer, p, the value of 0x12.
Integers will be 4 bytes in length:
+---+
p -> | 1 | 0x12
+---+
| 2 | 0x13
+---+
| 3 | 0x14
+---+
| 4 | 0x15
+---+
| 0 | 0x16
+---+
| 6 | 0x17
+---+
| 1 | 0x18
+---+
| 9 | 0x19
+---+
The integer at *p == 1234 (provided Big Endian layout).
Incrementing p will produce the address: 0x12 + 1 * sizeof(int), or
0x12 + 1 * (4) == 0x16.
+---+
p -> | 1 | 0x12
+---+
| 2 | 0x13
+---+
| 3 | 0x14
+---+
| 4 | 0x15
+---+
p + 1 -> | 0 | 0x16
+---+
| 6 | 0x17
+---+
| 1 | 0x18
+---+
| 9 | 0x19
+---+
Edit 2: Alignment
There is an issue of alignment at this perspective.
Let's assume the processor is 32-bits. It has an internal register (word) size of 32 bits (4 octets). The processor is defined to fetch 32-bits out of memory.
Let us store integers at addresses 4, 8, 12 and 16. The binary representation of those addresses:
0000 0100 -- 4
0000 1000 -- 8
0000 1100 -- 12
0001 0000 -- 16
As you can see, the rightmost 2 bits are always zero. The processor designer's don't need to implement the 2 rightmost address lines (thus saving money and real estate space). In this processor, fetches at address evenly divisible by 4 are most efficient (1 fetch). They are aligned to a 4-byte boundary.
I came through a program which was like below
firstMissingPositive(vector<int> &A) {
vector<bool> dict(A.size()+1,false);
for(int i=0;i<A.size();i++){
if(A[i]>0 && A[i]<dict.size()) dict[A[i]]=true;
}
if(A.size()==1 && A[0]!=1) return 1;
else if(A.size()==1 && A[0]==1) return 2;
int i=0;
for(i=1;i<dict.size();i++){
if(dict[i]==false) return i;
}
return i;
}
In this program, I could not get what is mean by following line
vector<bool> dict(A.size()+1,false);
What is dict and this statement?
It's simply a variable.
The definition of the variable calls a specific constructor of the vector to initialize it with a specific size, and initialize all elements to a specific value.
It's equivalent to
vector<bool> dict;
dict.resize(A.size()+1,false);
See e.g. this std::vector constructor reference for more information about available constructors.
It is an definition of a variable "dict" of type vector. And please Google it first
You are declaring container of bool's (it means variables which stores only 0/1 (8B)) which has same count of elements as int vector A and all these elements are set to false -> 0.
It calls this constructor
vector (size_type n, const value_type& val,
const allocator_type& alloc = allocator_type());
Example:
This is vector A:
0 1 2 3 4 <- Indexes
+---+---+---+---+---+
| 0 | 1 | 2 | 3 | 4 | (int)
+---+---+---+---+---+
Its size is 5, so it would declare container with size 5, initialized to 0's.
0 1 2 3 4 <- Indexes
+---+---+---+---+---+
| 0 | 0 | 0 | 0 | 0 | (bool)
+---+---+---+---+---+
In this case its used to flag indexes in first vectror.
For example it is often used for Sieve of Eratosthenes. You can set 1's to primes with each iteration. It would be (for numbers 0-4)
0 1 2 3 4
+---+---+---+---+---+
| 0 | 0 | 1 | 1 | 0 |
+---+---+---+---+---+
Then you know on which indexes are primes in vector A.
for (int i = 0; i < A.size(); i++)
{
if ( dict[i] == true )
{
std::cout << "Prime number: << A[i] << std::endl;
}
}
If I have a large array where the data streams are interleaved in some complex fashion, can I define a pointer p such that p + 1 is some arbitrary offset b bytes.
For example lets say I have 1,000,000 ints, 4 bytes each.
int* p_int = my_int_array;
This gives me *(p_int+1) == my_int_array[1] (moves 4 bytes)
I am looking for something like
something_here(b)* big_span_p_int = my_int_array;
which would make *(big_span_p_int + 1) == my_int_array[b] (moves 4*b or b bytes, whichever is possible or easier)
Is this possible? easy?
Thanks for the help.
EDIT:
b is compile time variable.
Using some of your code. There is no need to declare an additional pointer/array. Applying pointer arithmetic on p_int is enough to traverse and reach the number value you are seeking.
Let's look at this example:
int main() {
int my_int_array[5] {1,2,3,4,5};
int* p_int = my_int_array;
int b = 2;
std::cout << *(p_int + b) << std::endl; // Output is 3, because *p_int == my_int_array[0], so my_int_array[2] will give you the third index of the array.
}
Graphically represented:
Memory Address | Stored Value (values or memory addresses)
----------------------------------------------
0 | .....
1 | .....
2 | .....
3 | .....
4 | .....
5 | .....
6 | .....
7 | .....
8 | .....
. | .....
. | .....
. | .....
n-1 | .....
Imagine the memory as being a very big array in which you can access positions by its memory address (in this case we've simplified the addresses to natural numbers. In reality they're hexadecimal values). "n" is the total amount (or size) of the memory. Since Memory counts and starts in 0, size is equivalent to n-1.
Using the example above:
1. When you invoke:
int my_int_array[5] {1,2,3,4,5};
The Operating System and the C++ compiler allocates the integer array memory statically for you, but we can think that our memory has been changed. E.g. Memory address 2 (decided by the compiler) now has our first value of my_int_array.
Memory Address | Name - Stored Value (values or memory addresses)
-----------------------------------------------------
0 | .....
1 | .....
2 | my_int_array[0] = 1
3 | my_int_array[1] = 2
4 | my_int_array[2] = 3
5 | my_int_array[3] = 4
6 | my_int_array[4] = 5
7 | .....
8 | .....
. | .....
. | .....
. | .....
n-1 | .....
2. Now if we say:
int* p_int = my_int_array;
The memory changes again. E.g. Memory address 8 (decided by the compiler) now has a int pointer called *p_int.
Memory Address | Name - Stored Value (values or memory addresses)
-----------------------------------------------------
0 | .....
1 | .....
2 | my_int_array[0] = 1
3 | my_int_array[1] = 2
4 | my_int_array[2] = 3
5 | my_int_array[3] = 4
6 | my_int_array[4] = 5
7 | .....
8 | p_int = 2 (which means it points to memory address 2, which has the value of my_int_array[0] = 1)
. | .....
. | .....
. | .....
n-1 | .....
3. If in your program you now say:
p_int += 2; // You increase the value by 2 (or 8 bytes), it now points elsewhere, 2 index values ahead in the array.
Memory Address | Name - Stored Value (values or memory addresses)
-----------------------------------------------------
0 | .....
1 | .....
2 | my_int_array[0] = 1
3 | my_int_array[1] = 2
4 | my_int_array[2] = 3
5 | my_int_array[3] = 4
6 | my_int_array[4] = 5
7 | .....
8 | p_int = 4 (which means it points to memory address 4, which has the value of my_int_array[2] = 3)
. | .....
. | .....
. | .....
n-1 | .....
When doing memory allocation and pointer arithmetic in a simple case like this, you don't have to worry about the size in bytes of an int (4 bytes). The pointers here are already bound to a type (in this case int) when you declared them, so just by increasing their value by integer values, p_int + 1, this will make point p_int point to the next 4 bytes or int value. Just by adding the values to the pointers you will get the next integer.
If b is a constant expression (a compile-time constant), then pointer declared as
int (*big_span_p_int)[b]
will move by b * sizeof(int) bytes every time you increment it.
In C you can use a run-time value in place of b, but since your question is tagged [C++], this is not applicable.
I'm trying to get a definitive answer to this question.
Consider the following:
struct MyStruct {
uint16_t a : 4;
uint8_t b : 4;
}
Is this correct?
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
| | b | a |
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
Or is this correct?
+----+----+----+----+----+----+----+----+-----+----+----+----+----+----+----+----+----+
| | 22 | 21 | 20 | 19 | 18 | 17 | 16 | ... | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+----+----+----+----+----+----+----+----+-----+----+----+----+----+----+----+----+----+
| | b | ... | | a |
+----+----+----+----+----+----+----+----+-----+----+----+----+----+----+----+----+----+
I guess my question is three folds.
1. Does changing the type in the declaration sequence move the "cursor" to the next type boundary?
2. Is this an illegal declaration?
3. What do I do if I want the type to be the smallest size to represent its content? Assume I have a block of 64 bits that I want to split into bit fields. Do I need to declare every bit field as uint64_t myVar : 4 even if myVar fits into a uint8_t?
It depends.
The ISO/IEC 14882:1998 C++ standard (9.6 Bit-fields [class.bit]) says:
Allocation of bit-fields within a class object is implementation-defined.
Alignment of bit-fields is implementation-defined.
Bit-fields are packed into some addressable allocation unit.
[Note: bit-fields straddle allocation units on some machines and not on others. Bit-fields are assigned right-to-left on some machines, left-to-right on others. ]
So…
It's implementation defined.
It's not an illegal declaration.
There's no portable way to force the type to be as small as possible and you don't need to declare every bit field as uint64_t if it fits in a uint8_t.
How the bit fields are layed out is implementation defined;
there's no guarantee that you'll get either of these. But
a change in type does not require that the compiler go to some
new boundary. (On the other hand, using things like uint16_t
as a bit field type is a bit silly. The only reason to use
uint16_t is that for some reason, you need exactly 16
bits—a rarity in itself—and if you're using bit
fields, you'll obviously not get 16 bits.)