简体   繁体   中英

Validating the size of array in C

I am facing one issue regarding validating the size of array in C.

We have one .h file, which contains the constant value like
A 1
B 2
C 3
...
...
END 10

last element is END. Whenever any new constant is getting added, value of END should be increased. For eg if some added new constant call F which has value 10 then they have to increased the value of END from 10 to 11.

Now, in .c file we have one array of the size END. Each constant defined in .h file should have entry in this array, ie

abc[END] = {
1,
2,
1,
0,
...
...
}

Now, we have to add one test to check that, if anyone adding new entry in .h file and they have not added the corresponding entry in abc[] array, then test should fail. Earlier I have written the below piece of code to verify:

return (sizeof(tbl2schema)/sizeof(int) == END) ? TRUE : FALSE;

But I think, this code is not fulfilling the purpose. Hope, I have explained my issue.

Can anyone suggest me on this, How to do this check. I have to add a check that if anyone adding the entry in .h file then they have to add the entry in abc[] array.

The reason that your formula does not work is that you have explicitly specified END as the size. Remove it from the declaration to have the size of abc array change with the number of constants:

int abc[] = {
//     ^^
  1,
  2,
  1,
  0,
  ...
  ...
};

If someone forgets to add a constant after updating END now, the size of abc is not going to match.

Use static_assert to check the condition:

static_assert(sizeof(tbl2schema)/sizeof(int) == END, "abc size does not match.");

It seems the perfect usage for enum :

file.h :

typedef enum { A, B, C, END } constants_list;

file.c :

static int abc[END] = { 1, 2, 1 };

Your table abc will grow automatically as the list of constants grow. Note however that any value in the table will be initialized to 0 if not explicitly set in file.c .

Maybe you want:

return (sizeof(tbl2schema) / sizeof(int) > END) ? TRUE : FALSE;

which is equivalent of:

return sizeof(tbl2schema) / sizeof(int) > END;

Below is a C solution that does not compile if there is a discrepency between the header and the source file:

Header

enum {
    NAME_1,
    NAME_2,
    NAME_3,
    END
};

#define STATIC_ASSERT_ARRAY_SIZE(array, expected_size)     \
    static int static_assert_array_size_array_##array##_too_small \
        [sizeof(array)/sizeof(array[0]) - expected_size];  \
    static int static_assert_array_size_array_##array##_too_large \
        [expected_size - sizeof(array)/sizeof(array[0])]

Source #1

#include "header.h"
int data[] = { 5, 8 };
STATIC_ASSERT_ARRAY_SIZE(data, END);
int main(void)
{
    return 0;
}

Source #2

#include "header.h"
int data[] = { 5, 8, 10 };
STATIC_ASSERT_ARRAY_SIZE(data, END);
int main(void)
{
    return 0;
}

Source #3

#include "header.h"
int data[] = { 5, 8, 10, 11 };
STATIC_ASSERT_ARRAY_SIZE(data, END);
int main(void)
{
    return 0;
}

Compilation

$ gcc main1.c
main.c:3: error: size of array  static_assert_array_size_array_data_too_small  is too large
$


$ gcc main2.c
$


$ gcc main3.c
main.c:3: error: size of array  static_assert_array_size_array_data_too_large  is too large
$

This trick takes advantage from the fact that it is illegal to declare an array of negative size (you don't say !).

I'm assuming you've simplified the example a bit, but it looks like you just have a big array of values that you want to be able to access by name rather than with a "magic number" index—hence the #define s.

If that's the case, why aren't you just using a struct instead, with sequential fields named the same as your current symbolic names for the array indices?

struct { int A, B, C, ... } abc = {
1,
2,
3,
...
};

This way, the compiler will check to make sure you're not accessing an invalid name. You can still iterate over the struct members using pointers:

for (int *p = &abc.A; p <= &abc.Z; abc++) {
    do_something_with(*abc);
}

I would like to recommend a more general solution for the problem of interdependent, but scattered data in a program.

It's using a single preprocessor macro file which contains all information in a single place, in the form of a list of preprocessor function macros. The file is included wherever the information is needed to define data structures or types like enums.

In this example I use a list of paper size defintions; this served me nicely in real life when I wrote a PCL6 parser. In PCL6 each paper size is indeed a numerical token. Paper sizes have quite a few associated attributes, as well as a human readable name. Frequently the need occurs to map bidirectionally between name and token value and look up associated information. This leads to several data structures with redundant information plus a matching enum definition. It's easy to miss an update in one of them when adding a new paper type (in reality there are many dozen).

The trick is to define an entry in the macro file to a language construct which is suitable in the given place. Note how the language was carefully designed to allow trailing commas eg at the end of an enum definition or initializer lists. This is the use case for that.

We start with the file containing the macros which hold the information associated with a paper size. In reality there are of course many more, and more attributes.

//////////////////////////////////////////////////////
// papermacros.h
//////////////////////////////////////////////////////
// Has all information about paper sizes in one place.
// Note the function syntax. It's essential.

//       token, name, height, width
PAPERSIZE_MACRO(5, A5, 200, 150)
PAPERSIZE_MACRO(4, A4, 300, 200)
PAPERSIZE_MACRO(3, A3, 400, 300)

Then the paper class and enum. The macros are used to build an enum type of paper tokens which always contains all entries in the macro file. The enum element names are constructed with the preprocessor concatenation operator, names are constructed using the stringize operator. (We cannot have strings in the macro header right away because we want to use the name also as a base for the enum identifier -- there is no "unstringize" operator.)

//////////////////////////////////////////////////////
//  papers.h
//////////////////////////////////////////////////////
#include <string>
#include <map>
#include <sstream>

#undef PAPERSIZE_MACRO
#define PAPERSIZE_MACRO(token, name, height, width) \
    e_##name = token,

enum PaperSizeE {
    e_INVALID, // for default ctor
#   undef PAPERSIZE_MACRO
#   define PAPERSIZE_MACRO(token, name, height, width) \
    e_##name = token,
    // this included file expands to a series of enum definitions which 
    // make sure that each enum element is named 
    // like the paper name, with a prefix e_
#   include "papermacros.h"
    e_END // if you want. Note, however, that it has the (arbitrary)
        // value of the last "real" enum plus 1.
#undef PAPERSIZE_MACRO
}; 

class PaperT
{
    public: 

    PaperSizeE token;
    int height;
    int width;
    std::string name;

    PaperT(PaperSizeE t, std::string n, int h, int w)
    :token(t), name(n), height(h), width(w)
    {   }

    // Funny, needed by map resp. std::pair
    PaperT() : token(e_INVALID), name("invalid"), height(0), width(0) 
    {}

    std::string ToString()
    {
        std::ostringstream stm;
        stm << name << ", height: " << height << ", width: " << width;
        return stm.str();
    }

};

// Useful mappings. Paper information can now be
// efficiently looked up by token or by name.
extern std::map<PaperSizeE, PaperT> mapByToken;
extern std::map<std::string, PaperT> mapByName;

The next file contains the definitions for the maps declared above. Again the elements of the initiailzer list are constructed from the (multiple times) included macro header, with the respective suitable macro definitions. Here, too, trailing commas are ignored.

//////////////////////////////////////////////////////////////////
//  paperdefs.cpp
//////////////////////////////////////////////////////////////////

#include "papers.h"
using namespace std;

std::map<PaperSizeE, PaperT> mapByToken
{
#   define PAPERSIZE_MACRO(token, name, height, width) \
     {e_##name, PaperT(e_##name, #name, height, width) },

    // this expands to a series of 
    // { e_xx, PaperT(e_xx, "Ax", hhh, www) },
    // which is used to initialize the entries a map enum -> paper.
#   include "papermacros.h" 
#   undef PAPERSIZE_MACRO
};


std::map<string, PaperT> mapByName = 
{
#   define PAPERSIZE_MACRO(token, name, height, width) \
     {#name, PaperT(e_##name, #name, height, width) },
    // this expands to a series of 
    // { "Ax", PaperT(e_xx, "Ax", hhh, www) },
    // which is used to initialize the entries a map name -> paper.
#   include "papermacros.h" 
#   undef PAPERSIZE_MACRO
};

Last not least a main function in order to demonstrate usage.

//////////////////////////////////////////////////////////////////
//  main.cpp
//  Demonstrate how to use the paper related data structures.
//  Must be linked with paperdefs.o
//////////////////////////////////////////////////////////////////



#include "papers.h"
#include <iostream>

using namespace std;

int main()
{
    {
        PaperSizeE a4Token = e_A4;
        cout << "The name of the paper with token " << a4Token 
            << " is " << mapByToken[a4Token].name << endl;
    }
    {
        string name = "A3";
        cout << "The token val of the paper named " << name  
            << " is " << mapByName[name].token << endl;
    }       
    // iterate over all papers
    for(auto &p: mapByName)
    {
        cout << "Paper by name " << p.first << ": " 
            << p.second.ToString() << endl;
    }
}

The result:

$ g++ -std=c++11 -o papers main.cpp paperdefs.cpp && ./papers
The name of the paper with token 4 is A4
The token val of the paper named A3 is 3
Paper by name A3: A3, height: 400, width: 300
Paper by name A4: A4, height: 300, width: 200
Paper by name A5: A5, height: 200, width: 150

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM