Here what this problem looks like:
typedef uint32_t StrTableKey_t;
typedef struct StrTableEntry_s
{
char *string;
StrTableKey_t keyhash, keyid;
size_t usages;
} StrTableEntry_t;
typedef struct StrTable_s
{
StrTableEntry_t *entries;
StrTableKey_t count;
StrTableKey_t capacity;
} StrTable_t;
inline bool StrTableBinarySearchUnsafe (const StrTable_t *table, StrTableKey_t keyid, size_t *output)
{
size_t idxlow = 0, idxhigh = table->count - 1;
while (idxlow <= idxhigh)
{
size_t idxmid = (idxlow + idxhigh) >> 1;
if (table->entries[idxmid].keyid == keyid)
{
(*output) = idxmid;
return true;
}
else if (table->entries[idxmid].keyid > keyid)
{
// prevents overflow bug.
if (idxmid == 0)
{
break;
}
idxhigh = idxmid - 1;
}
else
{
idxlow = idxmid + 1;
}
}
return false;
}
char* StrTableGetValueByKey (const StrTable_t *table, StrTableKey_t keyid)
{
size_t idx;
if (table->count != 0 && StrTableBinarySearchUnsafe(table, keyid, &idx))
{
return table->entries[idx].string;
}
return NULL;
}
The StrTableBinarySearchUnsafe
is a common iteration to find an item inside the table and is used multiple times in the code.
From what I understand about inline, the compiler optimization will put all the instructions from StrTableBinarySearchUnsafe
into StrTableGetValueByKey
but those argouments that are passed (table, keyid, &idx
) will they be copied and defined twince as new variables or will the compiler use the same variables?
If the argouments are copied the stack call of StrTableGetValueByKey
should take almost twince his argouments, if they are not copied then if _StrTableBinarySearchUnsafe
changes them it will take effect into StrTableGetValueByKey
.
I have checked both with and whitout the compiler flag -O3 and it looks like they are copied or the compiler is smart enough to know what value should be copied, could I get confirmation about it?
EDIT 1
Thanks to @UlrichEckhardt to point out I was breaking the private indentifier rule documented here: https://en.cppreference.com/w/c/language/identifier
_StrTableBinarySearchUnsafe
shouldn't be used, it has been renamed to StrTableBinarySearchUnsafe
.Unsafe
to do the same instead.
- From what I understand about inline, the compiler optimization will put all the instructions from
StrTableBinarySearchUnsafe
intoStrTableGetValueByKey
The compiler might do so, or it might not. The inline
does not require it to do. And the compiler might or might not do the same even for functions declared without inline
. Do not be confused by the fact that the optimization you describe is called "inlining" -- the inline
keyword was inspired by inlining, but it does not require the compiler to perform inlining.
but those argouments that are passed (
table, keyid, &idx
) will they be copied and defined twince as new variables or will the compiler use the same variables?
If the compiler chooses to inline a function call, it is responsible for ensuring that the observable behavior is identical to what would be observed if it did not do so. Execution time may differ, however, and all manner of details that are not classified as "observable behavior" may do as well. C semantics are defined in terms of an abstract machine, and in the abstract machine, function arguments are copied in (shallowly). But although that often influences observable behavior, it is not itself classified as observable behavior.
The bottom line, then, is that C does not answer the specific question posed, and implementations might not even be consistent about it, but, generally speaking, you should not care.
- If the argouments are copied the stack call of
StrTableGetValueByKey
should take almost twince his argouments, if they are not copied then if_StrTableBinarySearchUnsafe
changes them it will take effect intoStrTableGetValueByKey
.
Those are among the considerations that an optimizing compiler may use in choosing how to optimize a function call or function implementation. There are many more. If you actually care about the details in specific cases then C probably is not the language for you. One of the main points of using a high-level language is that you don't have to sweat those details.
With that said, optimizing compilers typically provide a variety of options for influencing optimization strategy, perhaps even locally. This usually goes well beyond mere optimization level (-O1
, -O2
, etc). Consult your compiler's documentation if you really want to go down that rabbit hole.
- I have checked both with and whitout the compiler flag -O3 and it looks like they are copied or the compiler is smart enough to know what value should be copied, could I get confirmation about it?
Confirmed. See above.
- IIRC undescore whitout the uppercase are commonly used to mark something to be used privately inside a translation unit, I will keep the suffix Unsafe to do the same instead.
Identifiers that are to be used only inside one translation unit should be declared static
, which has exactly the effect of providing such isolation. Some projects do apply one or another Hungarian notation, which might include adorning static identifiers in some distinguishing way, but from where I sit, that does not appear to be particularly common in C. There are such conventions among practitioners of some other languages (Python comes to mind), but it is not reasonable to project that onto C.