I am attempting to port a library to Mac OS X. The compiler is reporting an incomplete type error. Specifically: field has incomplete type 'header_t []. However, when I look at the source code, header_t is defined just before packet_state_t, where packet_state_t references header_t. Thus, there shouldn't be any forward reference error, since header_t is clearly defined at the point at which it is referenced inside packet_state_t. The line in which the error occurs is marked with ERROR below. How to resolve?
typedef struct header_t {
uint8_t hdr_id; // header ID
uint8_t hdr_prefix; // length of the prefix (preamble) before the header
uint8_t hdr_gap; // length of the gap between header and payload
uint16_t hdr_flags; // flags for this header
uint16_t hdr_postfix; // length of the postfix (trailer) after the payload
uint32_t hdr_offset; // offset into the packet_t->data buffer
uint32_t hdr_length; // length of the header in packet_t->data buffer
uint32_t hdr_payload; // length of the payload
uint8_t hdr_subcount; // number of sub-headers
header_t *hdr_subheader; // Index of the first subheader in packet_t
jobject hdr_analysis; // Java JAnalysis based object if not null
} header_t;
typedef struct packet_state_t {
flow_key_t pkt_flow_key; // Flow key calculated for this packet, must be first
uint8_t pkt_flags; // flags for this packet
jobject pkt_analysis; // Java JAnalysis based object if not null
uint64_t pkt_frame_num; // Packet's frame number assigned by scanner
uint64_t pkt_header_map; // bit map of presence of headers
uint32_t pkt_wirelen; // Original packet size
uint32_t pkt_buflen; // Captured length
int8_t pkt_header_count; // total number of main headers found
header_t pkt_headers[]; // One per header + 1 more for payload ERROR HERE!!!
int8_t pkt_subheader_count; // total number of sub headers found
header_t pkt_subheaders[]; // One per header + 1 more for payload
} packet_state_t;
The type header_t
is just fine, but the compiler is actually complaining about the type header_t[]
, i.e. "array of indeterminate length of header_t
", which has an incomplete type because the compiler doesn't know how big it is (it couldn't possibly).
C99 (but not C89) supports what's called a flexible array member in structures, which is exactly this, but only at the end of a structure:
struct X {
// any member declarations
...
AnyType lastMemberArray[]; // This must be the LAST member
};
This is allowed, but it makes the structure you declared also an incomplete type because again, the compiler doesn't know how big it is. The only way to use it is to dynamically allocate memory of the required size or to cast an already allocated block of memory. For example:
// Allocate an X instance with space for 3 members in lastMemberArray:
X *x = malloc(sizeof(X) + 3 * sizeof(AnyType));
// Can now use x->lastMemberArray[0] through x->lastMemberArray[2]
...
// Alternatively:
char buffer[sizeof(X) + 3 * sizeof(AnyType)];
X *x = (X *)buffer;
// Same as above
Why does the flexible array member have to come last in the struct? Imagine if other members came after it. How could the compiler generate code to access those members?
// If this were allowed:
struct X {
AnyType flexibleArray[];
int memberAfter;
};
void doSomething(X *x) {
// How does the compiler generate code for this? It doesn't know what offset
// memberAfter is from the start of the object, because the array doesn't
// have a known size
printf("memberAfter = %d\n", x->memberAfter);
}
Hence, you can't have a structure with more than one flexible array member, because then clearly one of them wouldn't be the last structure member, so your definition is not allowed.
Whatever library code you're writing, it couldn't have used two flexible array members to begin with, or it wouldn't have compiled on any platform. I suggest you investigate the original code to find out what it did; if it's using standard ISO C features without relying on any platform-specific or implementation-specific behavior or extensions, you should have no trouble porting it.
Without being able to see the original code, I'd recommend you switch from using an inline flexible array member to a dynamically allocated array with a pointer, at least for the first array (and probably also the second for consistency):
typedef struct packet_state_t {
flow_key_t pkt_flow_key; // Flow key calculated for this packet, must be first
uint8_t pkt_flags; // flags for this packet
jobject pkt_analysis; // Java JAnalysis based object if not null
uint64_t pkt_frame_num; // Packet's frame number assigned by scanner
uint64_t pkt_header_map; // bit map of presence of headers
uint32_t pkt_wirelen; // Original packet size
uint32_t pkt_buflen; // Captured length
int8_t pkt_header_count; // total number of main headers found
header_t *pkt_headers; // POINTER here, not an array
int8_t pkt_subheader_count; // total number of sub headers found
header_t *pkt_subheaders; // POINTER here, not an array
} packet_state_t;
EDIT
I downloaded the jnetpcap code and compiled it on Linux to see what happened. To my surprise, it compiled. The compiler command invoked was:
gcc -c -fPIC -DLIBPCAP_VERSION=0x1532 -I/tmp/jnetpcap/build/include -I/tmp/jnetpcap/src/c -I/usr/lib/jvm/default-java/include -I/usr/lib/jvm/default-java/include/linux /tmp/jnetpcap/src/c/jnetpcap.cpp /tmp/jnetpcap/src/c/packet_flow.cpp /tmp/jnetpcap/src/c/packet_jheader.cpp /tmp/jnetpcap/src/c/jnetpcap_pcap_header.cpp /tmp/jnetpcap/src/c/nio_jbuffer.cpp /tmp/jnetpcap/src/c/winpcap_stat_ex.cpp /tmp/jnetpcap/src/c/winpcap_send_queue.cpp /tmp/jnetpcap/src/c/winpcap_ext.cpp /tmp/jnetpcap/src/c/util_debug.cpp /tmp/jnetpcap/src/c/util_crc16.c /tmp/jnetpcap/src/c/jnetpcap_ids.cpp /tmp/jnetpcap/src/c/jnetpcap_dumper.cpp /tmp/jnetpcap/src/c/jnetpcap_utils.cpp /tmp/jnetpcap/src/c/util_in_cksum.cpp /tmp/jnetpcap/src/c/jnetpcap_beta.cpp /tmp/jnetpcap/src/c/nio_jmemory.cpp /tmp/jnetpcap/src/c/util_crc32.c /tmp/jnetpcap/src/c/packet_jsmall_scanner.cpp /tmp/jnetpcap/src/c/mac_addr_sys.c /tmp/jnetpcap/src/c/packet_protocol.cpp /tmp/jnetpcap/src/c/nio_jnumber.cpp /tmp/jnetpcap/src/c/packet_jheader_scanner.cpp /tmp/jnetpcap/src/c/library.cpp /tmp/jnetpcap/src/c/packet_jscan.cpp /tmp/jnetpcap/src/c/jnetpcap_pcap100.cpp /tmp/jnetpcap/src/c/mac_addr_dlpi.c /tmp/jnetpcap/src/c/util_checksum.cpp /tmp/jnetpcap/src/c/packet_jpacket.cpp /tmp/jnetpcap/src/c/winpcap_ids.cpp /tmp/jnetpcap/src/c/jnetpcap_bpf.cpp
So the first thing that's going on here is that this is C++, not C. C++ does not support flexible array members at all, although some compilers support them as extensions. C++03 §9.2/8 says:
[...] When an array is used as the type of a nonstatic member all dimensions shall be specified.
And C++11 §9.2/9 says:
9 Non-static (9.4) data members shall not have incomplete types. [...]
When I compile this code in g++ 4.8.2 with a higher warning level (-Wall -Wextra pedantic
), it warns as follows:
In file included from /tmp/jnetpcap/src/c/packet_jscan.cpp:28:0:
/tmp/jnetpcap/src/c/packet_jscanner.h:287:23: warning: ISO C++ forbids zero-size array ‘pkt_headers’ [-Wpedantic]
header_t pkt_headers[]; // One per header + 1 more for payload
^
/tmp/jnetpcap/src/c/packet_jscanner.h:290:26: warning: ISO C++ forbids zero-size array ‘pkt_subheaders’ [-Wpedantic]
header_t pkt_subheaders[]; // One per header + 1 more for payload
So what g++ is doing (contrary to the C++ standard) is converting the arrays of unspecified size to arrays of size 0. The first one (pkt_headers
) works exactly like the flexible array member in C99, in that if you've allocated the proper amount of memory, you can access the array members of that normally up to the maximum size. But if you ever access any members after that (particularly pkt_subheader_count
and pkt_subheaders
), the compiler generates code as if pkt_headers
had size 0, i.e. if the struct were equivalent to this:
typedef struct packet_state_t {
flow_key_t pkt_flow_key; // Flow key calculated for this packet, must be first
uint8_t pkt_flags; // flags for this packet
jobject pkt_analysis; // Java JAnalysis based object if not null
uint64_t pkt_frame_num; // Packet's frame number assigned by scanner
uint64_t pkt_header_map; // bit map of presence of headers
uint32_t pkt_wirelen; // Original packet size
uint32_t pkt_buflen; // Captured length
int8_t pkt_header_count; // total number of main headers found
// NOTHING HERE (array of size 0)
int8_t pkt_subheader_count; // total number of sub headers found
header_t pkt_subheaders[]; // One per header + 1 more for payload
} packet_state_t;
Which would cause accesses to pkt_subheader_count
(and probably also access to pkt_subheaders
) to access the exact same memory as as pkt_headers[0]
.
Why does the happen to work out ok? Because the code in this project never accesses pkt_subheader_count
or pkt_subheaders
anywhere. If it did, the code wouldn't work for the reasons described above unless it got extraordinarily lucky. And it's not valid C++, it just happens to be accepted by the compiler.
Solution? Just remove pkt_subheader_count
and pkt_subheaders
from the structure declaration. They're not used anywhere in code, and removing them allows pkt_headers[]
to be the last member of the structure, so it's a valid flexible array member, which is valid C99 or a non-standard compiler extension in C++.
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments