casting 115bit data to union of bitfields gets erroneous results - c++

I'm trying to cast a 115bit data to a union of bitfields and getting a wrong result.
Setup
I have two types of data:
Configuration: data[62:0]
addr[113:107]
type[114]
RawBits: lowBits[63:0]
highBits[115:64]
So I defined the following bitfields and union:
typedef struct RawBits {
unsigned long int lowBits:64;
unsigned long int highBits:51;
unsigned long int reserved :13;
} __attribute__((packed))rawBits;
typedef struct Configuration {
unsigned long int data : 62;
unsigned long int addr : 7;
unsigned long int type : 1;
unsigned long int reserved : 58;
} __attribute__((packed))Configuration ;
typedef union Instruction {
RawBits bits;
Configuration configuration;
} __attribute__((packed))Instruction;
As data I used:
uint8_t configuration_test[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xfc, 0x00,
And to convert the buffer to the union type I used simple casting:
Instruction *instruction = (Instruction *)configuration_test;
Expected result
instruction->bits->lowBits = 0xfffffffffffffffc
instruction->bits->highBits = 0x00000000000001fc
instruction->bits->reserved = 0x0000000000000000
instruction->configuration->data = 0x3fffffffffffffff
instruction->configuration->addr = 0x000000000000007f
instruction->configuration->type = 0x0000000000000000
instruction->configuration->reserved = 0x0000000000000000
Real result
instruction->bits->lowBits = 0xfcffffffffffffff
instruction->bits->highBits = 0x0004010000000000
instruction->bits->reserved = 0x000000000000001f
instruction->configuration->data = 0x3cffffffffffffff
instruction->configuration->addr = 0x0000000000000003
instruction->configuration->type = 0x0000000000000000
instruction->configuration->reserved = 0x0003f00400000000

Related

Converting / Parsing C++ byte struct to Go

I am reading some data packets in Go, where the fields are C++ data types. I tried parsing the data but I am reading garbage values.
Here is a small example - the data spec sheet for a particular datatype is as follows in C++,
struct CarTelemetryData
{
uint16 m_speed;
uint8 m_throttle;
int8 m_steer;
uint8 m_brake;
uint8 m_clutch;
int8 m_gear;
uint16 m_engineRPM;
uint8 m_drs;
uint8 m_revLightsPercent;
uint16 m_brakesTemperature[4];
uint16 m_tyresSurfaceTemperature[4];
uint16 m_tyresInnerTemperature[4];
uint16 m_engineTemperature;
float m_tyresPressure[4];
};
And below is what I have defined in Go
type CarTelemetryData struct {
Speed uint16
Throttle uint8
Steer int8
Brake uint8
Clutch uint8
Gear int8
EngineRPM uint16
DRS uint8
RevLightsPercent uint8
BrakesTemperature [4]uint16
TyresSurfaceTemperature [4]uint16
TyresInnerTemperature [4]uint16
EngineTemperature uint16
TyresPressure [4]float32
}
For the actual un-marshalling, I am doing this -
func decodePayload(dataStruct interface{}, payload []byte) {
dataReader := bytes.NewReader(payload[:])
binary.Read(dataReader, binary.LittleEndian, dataStruct)
}
payload := make([]byte, 2048)
s.conn.ReadFromUDP(payload[:])
telemetryData := &data.CarTelemetryData{}
s.PacketsRcvd += 1
decodePayload(telemetryData, payload)
I suspect that this is because the datatypes are not equivalent and there is some conversion issue while reading the bytes into Go data-types, whereas they have been originally packages as C++. How can I deal with this?
Note: I don't have any control over the data that is sent, this is sent by a third party service.
The issue you're facing has to do with the alignment of struct members. You can read more about it here but, in short, the C++ compiler will sometimes add padding bytes in order to maintain the natural alignment expected by the architecture. If that alignment is not used, it may cause degraded performance or even an access violation.
For x86/x64, for example, the alignment of most types will usually (but not necessarily guaranteed to) be the same as the size. We can see that
#include <cstdint>
#include <type_traits>
std::size_t offsets[] = {
std::alignment_of_v<std::uint8_t>,
std::alignment_of_v<std::uint16_t>,
std::alignment_of_v<std::uint32_t>,
std::alignment_of_v<std::uint64_t>,
std::alignment_of_v<__uint128_t>,
std::alignment_of_v<std::int8_t>,
std::alignment_of_v<std::int16_t>,
std::alignment_of_v<std::int32_t>,
std::alignment_of_v<std::int64_t>,
std::alignment_of_v<__int128_t>,
std::alignment_of_v<float>,
std::alignment_of_v<double>,
std::alignment_of_v<long double>,
std::alignment_of_v<void*>,
};
compiles to
offsets:
.quad 1
.quad 2
.quad 4
.quad 8
.quad 16
.quad 1
.quad 2
.quad 4
.quad 8
.quad 16
.quad 4
.quad 8
.quad 16
.quad 8
Due to these (and other) implementation details, it may be advisable to not rely on the internal representation. In some cases, however, other methods may not be fast enough (such as serializing field by field), or you may not be able to change the C++ code, like OP.
binary.Read expects packed data, but C++ will use padding. We need to either use a compiler-dependent directive such as #pragma pack(1) or add padding the Go struct. The first is not an option for OP, so we'll use the second.
We can use the offsetof macro to determine the offset of a struct member relative to the struct itself. We can do something like
#include <array>
#include <cstddef>
#include <cstdint>
using int8 = std::int8_t;
using uint8 = std::uint8_t;
using uint16 = std::uint16_t;
struct CarTelemetryData {
uint16 m_speed;
uint8 m_throttle;
int8 m_steer;
uint8 m_brake;
uint8 m_clutch;
int8 m_gear;
uint16 m_engineRPM;
uint8 m_drs;
uint8 m_revLightsPercent;
uint16 m_brakesTemperature[4];
uint16 m_tyresSurfaceTemperature[4];
uint16 m_tyresInnerTemperature[4];
uint16 m_engineTemperature;
float m_tyresPressure[4];
};
// C++ has no reflection (yet) so we need to list every member
constexpr auto offsets = std::array{
offsetof(CarTelemetryData, m_speed),
offsetof(CarTelemetryData, m_throttle),
offsetof(CarTelemetryData, m_steer),
offsetof(CarTelemetryData, m_brake),
offsetof(CarTelemetryData, m_clutch),
offsetof(CarTelemetryData, m_gear),
offsetof(CarTelemetryData, m_engineRPM),
offsetof(CarTelemetryData, m_drs),
offsetof(CarTelemetryData, m_revLightsPercent),
offsetof(CarTelemetryData, m_brakesTemperature),
offsetof(CarTelemetryData, m_tyresSurfaceTemperature),
offsetof(CarTelemetryData, m_tyresInnerTemperature),
offsetof(CarTelemetryData, m_engineTemperature),
offsetof(CarTelemetryData, m_tyresPressure),
};
constexpr auto sizes = std::array{
sizeof(CarTelemetryData::m_speed),
sizeof(CarTelemetryData::m_throttle),
sizeof(CarTelemetryData::m_steer),
sizeof(CarTelemetryData::m_brake),
sizeof(CarTelemetryData::m_clutch),
sizeof(CarTelemetryData::m_gear),
sizeof(CarTelemetryData::m_engineRPM),
sizeof(CarTelemetryData::m_drs),
sizeof(CarTelemetryData::m_revLightsPercent),
sizeof(CarTelemetryData::m_brakesTemperature),
sizeof(CarTelemetryData::m_tyresSurfaceTemperature),
sizeof(CarTelemetryData::m_tyresInnerTemperature),
sizeof(CarTelemetryData::m_engineTemperature),
sizeof(CarTelemetryData::m_tyresPressure),
};
constexpr auto computePadding() {
std::array<std::size_t, offsets.size()> result;
std::size_t expectedOffset = 0;
for (std::size_t i = 0; i < offsets.size(); i++) {
result.at(i) = offsets.at(i) - expectedOffset;
expectedOffset = offsets.at(i) + sizes.at(i);
}
return result;
}
auto padding = computePadding();
which compiles to (constexpr FTW)
padding:
.quad 0
.quad 0
.quad 0
.quad 0
.quad 0
.quad 0
.quad 1
.quad 0
.quad 0
.quad 0
.quad 0
.quad 0
.quad 0
.quad 2
So, on x86, we need one byte before EngineRPM and two bytes before TyresPressure.
So, let's check if that works.
C++:
#include <cstddef>
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <span>
using int8 = std::int8_t;
using uint8 = std::uint8_t;
using uint16 = std::uint16_t;
struct CarTelemetryData {
uint16 m_speed;
uint8 m_throttle;
int8 m_steer;
uint8 m_brake;
uint8 m_clutch;
int8 m_gear;
uint16 m_engineRPM;
uint8 m_drs;
uint8 m_revLightsPercent;
uint16 m_brakesTemperature[4];
uint16 m_tyresSurfaceTemperature[4];
uint16 m_tyresInnerTemperature[4];
uint16 m_engineTemperature;
float m_tyresPressure[4];
};
int main() {
CarTelemetryData data = {
.m_speed = 1,
.m_throttle = 2,
.m_steer = 3,
.m_brake = 4,
.m_clutch = 5,
.m_gear = 6,
.m_engineRPM = 7,
.m_drs = 8,
.m_revLightsPercent = 9,
.m_brakesTemperature = {10, 11, 12, 13},
.m_tyresSurfaceTemperature = {14, 15, 16, 17},
.m_tyresInnerTemperature = {18, 19, 20, 21},
.m_engineTemperature = 22,
.m_tyresPressure = {23, 24, 25, 26},
};
std::cout << "b := []byte{" << std::hex << std::setfill('0');
for (auto byte : std::as_bytes(std::span(&data, 1))) {
std::cout << "0x" << std::setw(2) << static_cast<unsigned>(byte)
<< ", ";
}
std::cout << "}";
}
results in
b := []byte{0x01, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00, 0x07, 0x00, 0x08, 0x09, 0x0a, 0x00, 0x0b, 0x00, 0x0c, 0x00, 0x0d, 0x00, 0x0e, 0x00, 0x0f, 0x00, 0x10, 0x00, 0x11, 0x00, 0x12, 0x00, 0x13, 0x00, 0x14, 0x00, 0x15, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x41, 0x00, 0x00, 0xc0, 0x41, 0x00, 0x00, 0xc8, 0x41, 0x00, 0x00, 0xd0, 0x41, }
Let's use that in Go:
// Type your code here, or load an example.
// Your function name should start with a capital letter.
package main
import (
"bytes"
"encoding/binary"
"fmt"
)
type CarTelemetryData struct {
Speed uint16
Throttle uint8
Steer int8
Brake uint8
Clutch uint8
Gear int8
_ uint8
EngineRPM uint16
DRS uint8
RevLightsPercent uint8
BrakesTemperature [4]uint16
TyresSurfaceTemperature [4]uint16
TyresInnerTemperature [4]uint16
EngineTemperature uint16
_ uint16
TyresPressure [4]float32
}
func main() {
b := []byte{0x01, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00, 0x07, 0x00, 0x08, 0x09, 0x0a, 0x00, 0x0b, 0x00, 0x0c, 0x00, 0x0d, 0x00, 0x0e, 0x00, 0x0f, 0x00, 0x10, 0x00, 0x11, 0x00, 0x12, 0x00, 0x13, 0x00, 0x14, 0x00, 0x15, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x41, 0x00, 0x00, 0xc0, 0x41, 0x00, 0x00, 0xc8, 0x41, 0x00, 0x00, 0xd0, 0x41}
var dataStruct CarTelemetryData
dataReader := bytes.NewReader(b[:])
binary.Read(dataReader, binary.LittleEndian, &dataStruct)
fmt.Printf("%+v", dataStruct)
}
which prints
{Speed:1 Throttle:2 Steer:3 Brake:4 Clutch:5 Gear:6 _:0 EngineRPM:7 DRS:8 RevLightsPercent:9 BrakesTemperature:[10 11 12 13] TyresSurfaceTemperature:[14 15 16 17] TyresInnerTemperature:[18 19 20 21] EngineTemperature:22 _:0 TyresPressure:[23 24 25 26]}
Take the padding bytes out and it fails.

Allocate a type to a uint8_t value

In my project I read the unique ID from an RFID tag, the result is in the form uint8_t TagRead[4].
The result is compared with a number of predefined tag ID values to establish which tag has been read.
For example:
uint8_t RED1[4] = { 0x73, 0xD5, 0xB7, 0xAC };
uint8_t RED2[4] = { 0x7E, 0x27, 0x49, 0x4E };
uint8_t RED3[4] = { 0x02, 0xFD, 0x06, 0x40 };
uint8_t GREEN1[4] = { 0xAB, 0xEC, 0x68, 0x80 };
uint8_t GREEN2[4] = { 0xEE, 0x20, 0x50, 0x4E };
uint8_t GREEN3[4] = { 0x27, 0x06, 0x40, 0x73 };
if (*((uint32_t *)TagRead) == *((uint32_t *)RED2)) {
// RED2 tag has been read
}
else if (*((uint32_t *)TagRead) == *((uint32_t *)GREEN3)) {
// GREEN3 tag has been read
}
My question relates to being able to assign a type/category to a group of tags so that an action can be performed based on the colour of the tag that has been scanned.
It may be that when a RED tag is scanned we switch on a red LED and when a GREEN tag is scanned we switch on a blue LED.
Because there are approximately 50 tags of each colour, I don't want to to have to list all the tag names in the If statement. Instead, is it possible to assign the colour to the tag?
It would then be possible to do:
If scanned tag is of type RED, do red action.
If scanned tag is of type GREEN do green action.
Thanks for your help.
You can create a structure with id and a color enum:
enum class Color { red, green };
struct Tag
{
uint8_t id[4];
Color color;
};
Tag RED1 = { { 0x73, 0xD5, 0xB7, 0xAC }, Color::red } ;
Tag RED2 = { { 0x7E, 0x27, 0x49, 0x4E }, Color::red } ;
Tag RED3 = { { 0x02, 0xFD, 0x06, 0x40 }, Color::red } ;
Tag GREEN1 = { { 0xAB, 0xEC, 0x68, 0x80 }, Color::green } ;
Tag GREEN2 = { { 0xEE, 0x20, 0x50, 0x4E }, Color::green } ;
Tag GREEN3 = { { 0x27, 0x06, 0x40, 0x73 }, Color::green } ;
void test(Tag tag)
{
if (tag.color == Color::red)
{
//
}
else if (tag.color == Color::green)
{
}
}
First, your comparison is undefined behaviour. The right way to go is with a std::memcmp. You also need to take care of endianness.
In order to attach properties (like color) to your tags, simply define a struct:
struct rfid_tag
{
uint8_t value[4];
enum { ... } color;
};
Once you got a struct, you can enrich it with operator== so you can use std::find() to lookup the appropriate tag in one line:
#include <iostream>
#include <array>
#include <algorithm>
#include <cstring>
struct rfid_tag
{
enum color_type { red = 10, blue = 11 };
std::array<uint8_t, 4> value;
color_type color;
};
bool operator==(std::array<uint8_t, 4> const& tagvalue, rfid_tag const& rhs)
{
return std::memcmp(tagvalue.data(), rhs.value.data(), rhs.value.size()) == 0;
}
bool operator==(rfid_tag const& lhs, std::array<uint8_t, 4> const& tagvalue)
{
return tagvalue == lhs;
}
static const std::array<rfid_tag, 3> known_tags = {
rfid_tag{ { 0x00, 0x01, 0x02, 0x03 }, rfid_tag::red },
rfid_tag{ { 0x10, 0x11, 0x12, 0x13 }, rfid_tag::blue },
rfid_tag{ { 0x20, 0x21, 0x22, 0x23 }, rfid_tag::red }
};
int main()
{
const std::array<uint8_t, 4> tag_to_find{ 0x10, 0x11, 0x12, 0x13 };
std::cout << std::find(begin(known_tags), end(known_tags), tag_to_find)->color << "\n"; // outputs "11" as expected
}
demo
There are multiple ways.
You could write a struct which contains your tag along with your color, like this:
struct ColoredTag
{
uint8_t[4] value;
std::string color;
} typename ColoredTag_t;
ColoredTag_t RED1 = {{ 0x73, 0xD5, 0xB7, 0xAC }, "Red"};
ColoredTag_t RED2 = {{ 0x7E, 0x27, 0x49, 0x4E }, "Red"};
ColoredTag_t RED3 = {{ 0x02, 0xFD, 0x06, 0x40 }, "Red"};
ColoredTag_t GREEN1 = {{ 0xAB, 0xEC, 0x68, 0x80 }, "Green"};
ColoredTag_t GREEN2 = {{ 0xEE, 0x20, 0x50, 0x4E }, "Green"};
ColoredTag_t GREEN3 = {{ 0x27, 0x06, 0x40, 0x73 }, "Green"};
Or, you can use a std::map to assign a color to a tag, like this
std map<uint8_t[4], std::string> tags;
public void fillTags()
{
tags[RED1] = "Red";
tags[RED2] = "Red";
//...
}
std::string getColor(uint8_t tag)
{
return tags[tag];
}
There might be some more solutions for this issue, but these are the ones that came to my mind first.

Function returning wrong value in lpc824

I am working on porting an application running on Arduino Mega to LPC824. The following piece of code is working differently for both the platforms.
/**
* Calculation of CMAC
*/
void cmac(const uint8_t* data, uint8_t dataLength) {
uint8_t trailer[1] = {0x80};
uint8_t bytes[_lenRnd];
uint8_t temp[_lenRnd];
memcpy(temp, data, dataLength);
concatArray(temp, dataLength, trailer, 1);
dataLength ++;
addPadding(temp, dataLength);
memcpy(bytes, _sk2, _lenRnd);
xorBytes(bytes,temp,_lenRnd);
aes128_ctx_t ctx;
aes128_init(_sessionkey, &ctx);
uint8_t* chain = aes128_enc_sendMode(bytes, _lenRnd, &ctx, _ivect);
Board_UARTPutSTR("chain\n\r");
printBytes(chain, 16, true);
memcpy(_ivect, chain, _lenRnd);
//memcpy(_ivect, aes128_enc_sendMode(bytes,_lenRnd,&ctx,_ivect), _lenRnd);
memcpy(_cmac,_ivect, _lenRnd);
Board_UARTPutSTR("Initialization vector\n\r");
printBytes(_ivect, 16, true);
}
I am expecting a value like {0x5d, 0xa8, 0x0f, 0x1f, 0x1c, 0x03, 0x7f, 0x16, 0x7e, 0xe5, 0xfd, 0xf3, 0x45, 0xb7, 0x73, 0xa2} for the chain variable. But the follow function is working differently. The print inside the function has the correct value which I want ({5d, 0xa8, 0x0f, 0x1f, 0x1c, 0x03, 0x7f, 0x16, 0x7e, 0xe5, 0xfd, 0xf3, 0x45, 0xb7, 0x73, 0xa2}).
But when the function returns chain is having a different value, compared to what I am expecting, I get the following value for chain {0x00, 0x20, 0x00, 0x10, 0x03, 0x01, 0x00, 0x00, 0xd5, 0x00, 0x00, 0x00, 0xd7, 0x00, 0x00, 0x00}
Inside the function, the result is correct. But it returns a wrong value to the function which called it. Why is it happening so ?
uint8_t* aes128_enc_sendMode(unsigned char* data, unsigned short len, aes128_ctx_t* key,
const unsigned char* iv) {
unsigned char tmp[16];
uint8_t chain[16];
unsigned char c;
unsigned char i;
memcpy(chain, iv, 16);
while (len >= 16) {
memcpy(tmp, data, 16);
//xorBytes(tmp,chain,16);
for (i = 0; i < 16; i++) {
tmp[i] = tmp[i] ^ chain[i];
}
aes128_enc(tmp, key);
for (i = 0; i < 16; i++) {
//c = data[i];
data[i] = tmp[i];
chain[i] = tmp[i];
}
len -= 16;
data += 16;
}
Board_UARTPutSTR("Chain!!!:");
printBytes(chain, 16, true);
return chain;
}
A good start with an issue like this is to delete as much as you can while reproducing the error, with a minimal code example the answer is typically clear. I have done that for you here.
uint8_t* aes128_enc_sendMode(void) {
uint8_t chain[16];
return chain;
}
The chain variable is a local to the function, it ceases to be defined once the function exists. Accessing a pointer to that variable causes undefined behaviour, don't do it.
In practice the pointer to the array still exists and points to an arbitrary block of memory. This block of memory is no longer reserved and can be overwritten at any time.
I suspect it works for the AVR because it is a simple 8 bit chip and that piece of memory was sitting unmolested by the time you used it. The ARM would have used greater optimisations, possibly running the full array on registers, so the data doesn't survive the transition.
tldr; You need to malloc() any arrays that you want to live past the function's exit. Be careful, malloc and embedded systems go together like diesel and styrofoam, it gets messy real quick.

Send Hex Bytes To A Serial Port

I am attempting to send hexadecimal bytes to a serial com port. The issue is that the segment that sends the command apparently wants a system string instead of an integer (error C2664 "cannot convert parameter 1 from 'int' to 'System::String ^'). I have looked for a way to send an integer instead but have had no luck. (I have tried sending string representations of the hexadecimal values, but the device did not recognize the commands)
Main part of Code
private: System::Void poll_Click(System::Object^ sender, System::EventArgs^ e)
{
int i, end;
double a = 1.58730159;
String^ portscan = "port";
String^ translate;
std::string portresponse [65];
std::fill_n(portresponse, 65, "Z");
for (i=1;i<64;i++)
{
if(this->_serialPort->IsOpen)
{
// Command 0 generator
int y = 2;
y += i;
int command0[10] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x02, dectohex(i), 0x00, 0x00, dectohex(y)};
for (end=0;end<10;end++)
{
this->_serialPort->WriteLine(command0[end]);
}
translate = (this->_serialPort->ReadLine());
MarshalString(translate, portresponse [i]);
if(portresponse [i] != "Z")
{
comboBox7->Items->Add(i);
}
this->progressBar1->Value=a;
a += 1.58730159;
}
}
}
Here is the function dectohex:
int dectohex(int i)
{
int x = 0;
char hex_array[10];
sprintf (hex_array, "0x%02X", i);
string hex_string(hex_array);
x = atoi(hex_string.c_str());
return x;
}
This is what solved my problem, courtesy of Jochen Kalmbach
auto data = gcnew array<System::Byte> { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x02, 0xBF, 0x00, 0x00, 0xBD };
_serialPort->Write(data, 0, data->Length);
Replaced this
this->_serialPort->WriteLine(command0[end]);
You cannot sent an integer over a serial line.... you can only sent BYTES (7-8 bit)!
You need to choose what you want to do:
Sent characters: So the "number" 12 will be converted into the bytes
_serialPort->Write(12.ToString());
// => 0x49, 0x50
Sent the integer (4 bytes) as little endian
auto data = System::BitConverter::GetBytes(12);
_serialPort->Write(data, 0, data->Length);
// => 0x0c, 0x00, 0x00, 0x00
Or you write just a single byte:
auto data = gcnew array<System::Byte> { 12 };
_serialPort->Write(data, 0, data->Length);
// => 0x0c
Or write an byte array:
auto data = gcnew array<System::Byte> { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x02, 0xBF, 0x00, 0x00, 0xBD };
_serialPort->Write(data, 0, data->Length);
// => 0xFF 0xFF 0xFF 0xFF 0xFF 0x02 0xBF 0x00 0x00 0xBD

How to create table (array) with extern values?

I would like to create a static (file scope) table of data pointer, data size and data version. The problem is that the data are in external files, but constants in the extern files.
Example:
file1.c
const unsigned char data1[] =
{
0x65, 0xF0, 0xA8, 0x5F, 0x5F,
0x5F, 0x5F, 0x31, 0x32, 0x2E,
0x31, 0xF1, 0x63, 0x4D, 0x43,
0x52, 0x45, 0x41, 0x54, 0x45,
0x44, 0x20, 0x42, 0x59, 0x3A,
0x20, 0x69, 0x73, 0x70, 0x56,
// ...
};
const unsigned int data1_size =
sizeof(data1) / sizeof(data1[0]);
const unsigned int data1_version = 1;
file2.c
const unsigned char data2[] =
{
0x20, 0x44, 0x61, 0x74, 0x61,
0x20, 0x52, 0x6F, 0x77, 0x20,
0x3D, 0x20, 0x34, 0x38, 0x12,
//...
};
const unsigned int data2_size =
sizeof(data2) / sizeof(data2[0]);
const unsigned int data2_version = 1;
main.c
struct Data_Info
{
unsigned char * data_ptr;
unsigned int data_size;
unsigned int data_version;
};
extern const unsigned char data1[];
extern const unsigned int data1_size;
extern const unsigned int data1_version;
extern const unsigned char data2[];
extern const unsigned int data2_size;
extern const unsigned int data2_version;
static struct Data_Info Data_Info_Table[] =
{
// How to set this up??
// The compiler wants constant expressions here,
// and the extern is not considered a constant expression
// This is what I tried:
{ data1, data1_size, data1_version},
{ data2, data2_size, data2_version},
};
int main(void)
{
return 0;
}
I am using Green Hills Compiler, 4.2.3. The exact error message:
error #28, expression must have a constant value.
See my earlier post:
C: External const ints in a array of const struct
Note: C++ tag is included since this also applies to C++
Have you tried? (where X is 0 or 1.)
struct Data_Info
{
unsigned char const * data_ptr;
unsigned int data_size;
unsigned int data_version;
};
fileX.h
extern const struct Data_Info data_infoX;
fileX.c
const struct Data_Info data_infoX = { dataX, dataX_size, dataX_version };
main.c
const struct Data_Info const * Data_Info_Table[] =
{
&data_info1,
&data_info2
};
The problem is that the compiler doesn't know the value to place into the data_size and data_version members of the field. There are a couple of ways you can get around this without too much fuss.
Approach 1:
#include "data1.c"
#include "data2.c"
...
static struct Data_Info Data_Info_Table[] =
{
{ data1, sizeof(data1), 1},
{ data2, sizeof(data2), 1},
};
Approach 2:
struct Data_Info
{
unsigned char * data_ptr;
unsigned int * data_size;
unsigned int * data_version;
};
...
static struct Data_Info Data_Info_Table[] =
{
{ data1, &data1_size, &data1_version},
{ data2, &data2_size, &data2_version},
};
A third approach, as already demonstrated by jyoung, is to allocate the members in the separate data files and include their addresses in the Data_Info_Table.
When the constants are defined, use the extern keyword.
I am not that much of a C expert, but the order of initialization across different translation units is most probably undefined (I know it is undefined in C++, and it would be a huge break of compatibility if the behavior differed in C), so even if it was allowed by the compiler it would be dangerous.
Now, adding the C++ tag is most probably not going to help much, as AFAIK this is not only dependent on the language, but also in the specific version of the language: C89/C99. In C++ it works if you move the initialization to the declaration of the constant in the header file. Again, I am no C expert.