Author Topic: C functions with "volatile" return types - what does it mean?  (Read 2931 times)

0 Members and 1 Guest are viewing this topic.

Offline westfwTopic starter

  • Super Contributor
  • ***
  • Posts: 4354
  • Country: us
C functions with "volatile" return types - what does it mean?
« on: October 07, 2024, 12:11:49 am »
I was reading some freeRTOS documentation, and came across:

Code: [Select]
volatile TickType_t xTaskGetTickCount( void );
I don't think I understand what "volatile" does here, in the function definition.
A web search did not turn up any clear answers, except to clarify that this means that it is the return type (TickType_t) that is volatile, not "the function."

But I don't understand what it means for a return value to be "volatile."  Return values are almost exclusively put in some register, and they're effectively "frozen" from that point on.

I suppose that this could prevent optimization from eliding multiple calls to the function:
Code: [Select]
    x = xTaskGetTickCount();
    y = xTaskGetTickCount();  // yes, REALLY call the function again!
Except that I thought that that was default behavior for function calls, perhaps overrideable with "pure" and "const" attributes (at least in gcc.  Attributes are non-standard.)

Is a volatile return type just the opposite of the const attribute, enshrined in standardization?  Does it mean that at some point function return types WON'T be volatile by default?  (that will break a lot of things, I think.)  Does the optimization it's trying to prevent happen only when  functions are in-lined?  (and if so, isn't that redundant if the function body accesses volatile variables?)
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 15835
  • Country: fr
Re: C functions with "volatile" return types - what does it mean?
« Reply #1 on: October 07, 2024, 12:52:52 am »
Yes that would qualify the return type.

I tried thinking of contexts in which it could make a difference, but couldn't find any.

- If the function is defined in the same compilation unit, then whatever happens inside it will matter (as to whether it will be actually called or not) rather than its return type being qualified volatile or not.
- If it's defined externally, then it will get called unconditionally anyway, as the compiler can't know what the side-effects inside of it could be.

So, I don't think it has any practical purpose. Maybe it's in their "coding style" to match the return type exactly with the expression that is returned, although again here that shouldn't make any difference in terms of compiled code.

What I mean is:
xTaskGetTickCount() probably returns a global variable that is qualified volatile (a tick counter). So possibly their coding style requires the return type to match the returned expression, qualifiers included.
Just a guess. That may not be the rationale, but I know they have strict coding guidelines.

Now if anyone can find any real example of it making a difference, I'm all ears. I may have missed it.
 

Offline radiolistener

  • Super Contributor
  • ***
  • Posts: 4143
  • Country: 00
Re: C functions with "volatile" return types - what does it mean?
« Reply #2 on: October 07, 2024, 01:06:22 am »
I think it is likely marked as volatile to ensure that the compiler does not omit calls to xTaskGetTickCount() for optimization purposes, even if it assumes that the function xTaskGetTickCount() code will return the same value.
 
The following users thanked this post: bookaboo

Offline westfwTopic starter

  • Super Contributor
  • ***
  • Posts: 4354
  • Country: us
Re: C functions with "volatile" return types - what does it mean?
« Reply #3 on: October 07, 2024, 01:27:46 am »
(Apparently this function is not really volatile; it seems to be a documentation error.)
 
The following users thanked this post: SiliconWizard

Offline TheCalligrapher

  • Regular Contributor
  • *
  • Posts: 161
  • Country: us
Re: C functions with "volatile" return types - what does it mean?
« Reply #4 on: October 07, 2024, 04:07:58 am »
But I don't understand what it means for a return value to be "volatile."

What language are we talking here? C or C++?

In C language it means absolutely nothing. In C the return value is an rvalue and C does not support cv-qualification of rvalues. Meaning that you can legally apply such qualification in the source code, but it will be ignored anyway.

It is all natural aside from one corner case, which exists since C99: when the returned object is a struct with an array inside.

Code: [Select]
#include <stdio.h>

volatile struct S { int a[5]; } foo(void)
{
  return (struct S) { { 1, 2, 3, 4, 5 } };
}

int main()
{
  int *p;
  p = foo().a + 2, printf("%d\n", *p);
}

Should the compiler complain about a pointer to non-volatile trying to point to a volatile object? The standard says that the answer is still the same: the cv-qualificatin of the returned value is ignored, so there's no violation in the above example.
« Last Edit: October 07, 2024, 05:50:03 am by TheCalligrapher »
 
The following users thanked this post: newbrain

Offline Psi

  • Super Contributor
  • ***
  • Posts: 10396
  • Country: nz
Re: C functions with "volatile" return types - what does it mean?
« Reply #5 on: October 07, 2024, 04:28:10 am »
I tried to think about this, but all it did was hurt my brain.

Maybe it's one of those situations where you have to do something just to keep lint happy.
« Last Edit: October 07, 2024, 04:30:41 am by Psi »
Greek letter 'Psi' (not Pounds per Square Inch)
 

Offline golden_labels

  • Super Contributor
  • ***
  • Posts: 1483
  • Country: pl
Re: C functions with "volatile" return types - what does it mean?
« Reply #6 on: October 07, 2024, 05:36:40 am »
A qualifier placed before the type is actually an exception:(1) normally those go after the type. If it would be written in that pattern, you’d see TickType_t volatile xTaskGetTickCount(void). The association with TickType_t would be clearer, with no doubts about whether it applies to the type returned, the function, or something else.

It has been made clear in C99, and that interpretation continues with C23, that “(t)he properties associated with qualified types are meaningful only for expressions that are lvalues.” So for a return value it must be ignored and in fact gcc and clang will complain.(2)

If that quirk in the documentation is a remnant of the actual use of volatile on a return type, there may be an explanation. In the past 15–20 years major C compiler vendors leaned towards adhering to the standard C. But before that, and it still lingers among niche and in-house toolsets, the advertised C support was in fact “we don’t support C, we support ‘C flavour’”. While “flavour” sounds like just a minor deviation, in reality some were going right against the very core logic of the language.(3) For example Microsoft was not permitting freeing memory from const-qualified pointers and that’s just a tip of the iceberg of code-breaking incompatibilities. It is possible volatile was there at some point to circumvent or invoke some non-standard behavior of some old or niche compiler.


(1) I don’t know the history of how it came into existence. The original K&R C had no type qualifiers (like const). However, it had type specifiers (like short) and storage class specifiers (like register), which were placed before the type. I reckon it’s possible C99 permitted that order, because early type qualifiers followed the pattern, until it was recognized it’s wrong and they should actually be placed after the type they qualify. But this is only my own guess.
(2)
Code: [Select]
$ gcc -c -Wignored-qualifiers -o /dev/null wrong.c
wrong.c:1:5: warning: type qualifiers ignored on function return type [-Wignored-qualifiers]
    1 | int const f(void);
      |     ^~~~~

 $ clang -c -Wignored-qualifiers -o /dev/null wrong.c
wrong.c:1:5: warning: 'const' type qualifier on return type has no effect [-Wignored-qualifiers]
    1 | int const f(void);
      |   
-Wignored-qualifiers is enabled by -Wextra.
(3) And it shouldn’t be confused with what nowadays compilers may offer as an option, like clang and gcc with -std.
People imagine AI as T1000. What we got so far is glorified T9.
 
The following users thanked this post: DiTBho

Offline TheCalligrapher

  • Regular Contributor
  • *
  • Posts: 161
  • Country: us
Re: C functions with "volatile" return types - what does it mean?
« Reply #7 on: October 07, 2024, 05:48:56 am »
A qualifier placed before the type is actually an exception:(1) normally those go after the type.

No, there's nothing "normally" about it. Quite the opposite, the "normal" convention is to place it before the type. There are some individuals that seem to tout the "after the type" approach claiming there's some kind of logical rationale for it, but there really isn't.

If it would be written in that pattern, you’d see TickType_t volatile xTaskGetTickCount(void).

The authors of the declaration in question obviously did not adhere to the "after the type" convention.

The association with TickType_t would be clearer, with no doubts about whether it applies to the type returned, the function, or something else.

No, there's absolutely nothing clearer about it. In fact, one can argue that the "after the type" approach is misleading. That cv-qualifier belongs to the entire declaration, not to the first individual declarator. The "before the type" approach makes that perfectly clear, while the "after the type" approach tends to obfuscate that fact.

 
The following users thanked this post: newbrain

Offline golden_labels

  • Super Contributor
  • ***
  • Posts: 1483
  • Country: pl
Re: C functions with "volatile" return types - what does it mean?
« Reply #8 on: October 07, 2024, 06:11:00 am »
No, there's nothing "normally" about it. Quite the opposite, the "normal" convention is to place it before the type. There are some individuals that seem to tout the "after the type" approach claiming there's some kind of logical rationale for it, but there really isn't.
Forgive me for being too dim, but in my limited mental capability this simple argument is very convincing: one can’t write it before the type in almost all cases. I think my mind is so feeble, that I may be persuaded to think otherwise by seeing one counter-example. Just one, tiny qualified type example, that is not covered by this specific exception.

And, for others, a cabinet of curiosities:
Code: [Select]
int intCat(void);
long longCat(void);
long int stillLongCat(void);
int long anotherLongCat(void);
long int long veryLongCat(void);
int short shortCat(void);
int short signed oscillatingCat(void);
long signed long longOscillatingCat(void);
float floatingCat(void);
double long ultraPreciseCat(void);

// Strictly speaking valid, but avoid this one:
long extern int const long signed mutantCat(void);
People imagine AI as T1000. What we got so far is glorified T9.
 

Offline ejeffrey

  • Super Contributor
  • ***
  • Posts: 4044
  • Country: us
Re: C functions with "volatile" return types - what does it mean?
« Reply #9 on: October 07, 2024, 03:30:06 pm »
I tried to think about this, but all it did was hurt my brain.

Maybe it's one of those situations where you have to do something just to keep lint happy.

I think it's just a mistake.  Or an accidental side effect of poorly thought out code generation or preprocessor expansion.  It might be something someone tries when they are attempting to make a lint or compiler warning they don't understand go away.  No sensible tool should require it.  A useful tool probably should complain about it on the grounds that if you think it does anything you need to fix your understanding.

It could prevent optimizing out a function call, but that would be a compiler specific extension with no basis in the standard.  And it's unclear how that would work in practice.  The only way compilers optimize out function calls is if they first inline them.  If you want to prevent inlining, compilers have much clearer extensions to specify this.
 

Offline TheCalligrapher

  • Regular Contributor
  • *
  • Posts: 161
  • Country: us
Re: C functions with "volatile" return types - what does it mean?
« Reply #10 on: October 08, 2024, 03:57:04 am »
No, there's nothing "normally" about it. Quite the opposite, the "normal" convention is to place it before the type. There are some individuals that seem to tout the "after the type" approach claiming there's some kind of logical rationale for it, but there really isn't.
Forgive me for being too dim, but in my limited mental capability this simple argument is very convincing: one can’t write it before the type in almost all cases.

Um... What? I feel like there's some kind of gross misunderstanding involved here. Personally, I'm not aware of any cases where one "can’t write it before the type". Just consider this

Code: [Select]
const int ANSWER = 42;
Here you go. In the above example cv-qualifier `const` comes before the type. Nothing odd, unusual or surprising about it.
 

Offline golden_labels

  • Super Contributor
  • ***
  • Posts: 1483
  • Country: pl
Re: C functions with "volatile" return types - what does it mean?
« Reply #11 on: October 08, 2024, 05:33:46 am »
I asked for a case, that is not an exceptional one. This one is an exception. A simple (type, qualifier) pair, where qualifier may be placed before the type it applies to.

Normally a type qualifier list only goes after the type it applies to, because it belongs to a declarator, which is a suffix to that type. Not only is this a result of C approximating context-free grammar, and permits very natural and intuitive “stacking”, but it’s even hard to imagine how it could be written otherwise without copious amount of parentheses.

For reasons unknown to me, but for which a plausible explanation I suggested in a post above, there is an exception added. In declaration specifiers, which completely covers your (type, qualifier) pair without further parsing, a rule for qualifiers duplicates that of storage class specifiers (like extern or auto).(1) An unintended consequence of this rule being added are “monstrosities” from the zoo above. Even something like this:
Code: [Select]
int typedef x;
One note. I call that an exception, because I assume there is intentionality behind this. The intention would be allowing qualifiers to go before type in that special case. But this is nothing more than my assumption. Plain “laziness” without caring about allowing the prefix version would produce the same grammar, because it’s the easiest way to chain things. I prefer to believe that it was an exception added to allow people put things like const before types, but the alternative version is equally valid: unintended syntax, which happens to work in simple cases, becoming the convention.

I hope that resolves the confusion. Because a single sentence, tangentially relevant and uttered only as an introduction to a paragraph discussing another thing, a paragraph itself short and followed by two others, is taking considerable part of this thread already.


(1) C23 even dropped distinction between them in this context.
People imagine AI as T1000. What we got so far is glorified T9.
 

Offline peter-h

  • Super Contributor
  • ***
  • Posts: 4381
  • Country: gb
  • Doing electronics since the 1960s...
Re: C functions with "volatile" return types - what does it mean?
« Reply #12 on: October 08, 2024, 06:32:42 am »
Quote
I think it is likely marked as volatile to ensure that the compiler does not omit calls to xTaskGetTickCount() for optimization purposes, even if it assumes that the function xTaskGetTickCount() code will return the same value.

Is that legal C compiler behaviour?

Even if a function contained a constant expression like return(5) would the compiler be allowed to optimise away the call to it?
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline TheCalligrapher

  • Regular Contributor
  • *
  • Posts: 161
  • Country: us
Re: C functions with "volatile" return types - what does it mean?
« Reply #13 on: October 08, 2024, 06:55:58 am »
Quote
I think it is likely marked as volatile to ensure that the compiler does not omit calls to xTaskGetTickCount() for optimization purposes, even if it assumes that the function xTaskGetTickCount() code will return the same value.

Is that legal C compiler behaviour?

Even if a function contained a constant expression like return(5) would the compiler be allowed to optimise away the call to it?

C compilers follow "as if" principle when it comes to optimizations. The compiler is allowed to do absolutely anything, as long as the observable behavior of the program remains unchanged. But qualifying function return types as `volatile` does not make any difference in that regard. The compiler is allowed to omit absolutely any calls, as long as the observable behavior is not affected.

Since C compilers adhere to the principle of independent translation, the compiler is generally not allowed to assume anything about functions whose definitions are not present in the current translation unit. For this reason, compilers often provide extensions allowing the user to give it "hints" about the function's external behavior (e.g. `__attribute__((pure))`, `__attribute__((const))` and some such in GCC).

But I doubt that `volatile` in question is intended to serve as such a hint.
 

Offline peter-h

  • Super Contributor
  • ***
  • Posts: 4381
  • Country: gb
  • Doing electronics since the 1960s...
Re: C functions with "volatile" return types - what does it mean?
« Reply #14 on: October 08, 2024, 07:06:43 am »
That suggests that a function call would not be optimised away if the function was not in the current .c file, yes?

In the case of tick count, that should be ok because ultimately you are reading a hardware register or a memory address and that should be declared as volatile (usually in some .h file) so the call to tick count should not be optimised away no matter how much processing is done inside that function.
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline ejeffrey

  • Super Contributor
  • ***
  • Posts: 4044
  • Country: us
Re: C functions with "volatile" return types - what does it mean?
« Reply #15 on: October 08, 2024, 03:21:03 pm »
That suggests that a function call would not be optimised away if the function was not in the current .c file, yes?

Again, whether a function is "optimized away" is an implementation detail.  In practice, traditionally calls across translation units wouldn't be optimized.  But if you use LTO they can be.  If you are programming in C++ you really want LTO, but for most C programmers it doesn't make a huge difference.

Quote
In the case of tick count, that should be ok because ultimately you are reading a hardware register or a memory address and that should be declared as volatile (usually in some .h file) so the call to tick count should not be optimised away no matter how much processing is done inside that function.

Correct.  Properly declaring the behavior will result in the compiler doing the correct thing.  The function may still be inlined, but the hardware memory accesses won't be eliminated.
 

Offline ejeffrey

  • Super Contributor
  • ***
  • Posts: 4044
  • Country: us
Re: C functions with "volatile" return types - what does it mean?
« Reply #16 on: October 08, 2024, 03:44:27 pm »
Hmm...one possible edge case a volatile function might be intended for is to count as a step for the purposes of the forward progress guarantee.  That is, it would prevent removing infinite loops with no side effects.  I don't believe this is actually allowed by the standard, only volatile lvalue accesses count, not rvalues.  And it would be a perverse way to accomplish that when having a volatile local variable would be sufficient.  But I could imagine someone trying that, and I could imagine a compiler accidentally implementing that behavior.
 

Offline peter-h

  • Super Contributor
  • ***
  • Posts: 4381
  • Country: gb
  • Doing electronics since the 1960s...
Re: C functions with "volatile" return types - what does it mean?
« Reply #17 on: October 08, 2024, 03:47:11 pm »
I looked up LTO. Is this real in ARM32 GCC? I know the linker will recursively strip out code which is never called. But how can the linker implement non volatility?
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline TheCalligrapher

  • Regular Contributor
  • *
  • Posts: 161
  • Country: us
Re: C functions with "volatile" return types - what does it mean?
« Reply #18 on: October 08, 2024, 05:30:35 pm »
That suggests that a function call would not be optimised away if the function was not in the current .c file, yes?

Yes, precisely.

Modern implementations always try to expand the optimization capabilities, which includes global optimizations (aka "link-time" optimizations, LTOs). But I would guess that eliminating "unnecessary" function calls does not rank too high on the list of their development priorities. Users don't normally make unnecessary function calls.

If you are programming in C++ you really want LTO, but for most C programmers it doesn't make a huge difference.

I don't see how and why it would be different for C++ users vs. C users. Why really?
« Last Edit: October 08, 2024, 05:32:49 pm by TheCalligrapher »
 
The following users thanked this post: DiTBho

Offline golden_labels

  • Super Contributor
  • ***
  • Posts: 1483
  • Country: pl
Re: C functions with "volatile" return types - what does it mean?
« Reply #19 on: October 08, 2024, 05:47:56 pm »
In C this kind of volatile usage is meaningless, so a note should be taken: we’re now discussing a hypothetical behavior of what would be compiler-specific extension. Almost unconstrained by how C works.

While most often mentioned use of volatile is breaking continuity, there is another one: volatile operations form a totally ordered set. So a hypothetical use of volatile on a function could be interpreted as making a function a part of that set.
People imagine AI as T1000. What we got so far is glorified T9.
 
The following users thanked this post: newbrain

Offline peter-h

  • Super Contributor
  • ***
  • Posts: 4381
  • Country: gb
  • Doing electronics since the 1960s...
Re: C functions with "volatile" return types - what does it mean?
« Reply #20 on: October 08, 2024, 06:12:22 pm »
Quote
Users don't normally make unnecessary function calls.

Not intentionally, but a while ago my 450k of code suddenly became 12k - because my boot loader was entering main() via a long jump at base+32k (I will spare you the details; already comprehensively posted elsewhere) and the compiler was not clever enough to spot the jump, so it optimised away main() and everything inside it :)

Ultimately this debate is:

We, the GCC devs, produced a really good compiler around 2010, but we can't make ourselves unemployed, so what do we do? We go sideways... we go and look for weird ways to optimise away bits of code which the writer never wanted to get rid of.

I mean, somebody tell me, how much code is needed to recognise some loop structure, determine its limits, and replace it with memcpy or memset? They probably spent many man-months just doing that.
« Last Edit: October 08, 2024, 06:28:50 pm by peter-h »
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 9370
  • Country: fi
Re: C functions with "volatile" return types - what does it mean?
« Reply #21 on: October 08, 2024, 06:59:47 pm »
Even if a function contained a constant expression like return(5) would the compiler be allowed to optimise away the call to it?

Of course. Optimization is your friend, not your enemy.
 

Offline xvr

  • Frequent Contributor
  • **
  • Posts: 550
  • Country: ie
    • LinkedIn
Re: C functions with "volatile" return types - what does it mean?
« Reply #22 on: October 08, 2024, 07:03:42 pm »
I looked up LTO. Is this real in ARM32 GCC? I know the linker will recursively strip out code which is never called. But how can the linker implement non volatility?
It could, because it not a linker in LTO  :)
In LTO mode compiler do not generate code when compile source files, instead it produced IR (Intermediate Representation) of it (just parsed and machine readable form of source code).
On LTO link time compiler called again with all IR from all compilation units. This is almost the same as compiling whole program as one big *.c file. So compiler has all possibility to inline and remove whatever it want.
 
The following users thanked this post: newbrain

Offline westfwTopic starter

  • Super Contributor
  • ***
  • Posts: 4354
  • Country: us
Re: C functions with "volatile" return types - what does it mean?
« Reply #23 on: October 08, 2024, 09:41:17 pm »
Quote
Even if a function contained a constant expression like return(5) would the compiler be allowed to optimise away the call to it?
Quote
That suggests that a function call would not be optimised away if the function was not in the current .c file, yes?
This is what the (not standardized, yet) "pure" and "const" __attributes__ are for, at least theoretically.  If the .h file specifies that sin() has the const attribute, then the compiler can decide that multiple uses of sin(5) could be optimized away.  (I checked a couple math.h files, and they DON'T do this.)
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 15835
  • Country: fr
Re: C functions with "volatile" return types - what does it mean?
« Reply #24 on: October 08, 2024, 10:36:42 pm »
In C this kind of volatile usage is meaningless, so a note should be taken: we’re now discussing a hypothetical behavior of what would be compiler-specific extension. Almost unconstrained by how C works.

That's right. Yes, the OP has said long ago now that it was actually not in the code itself but a documentation error (maybe coming from older, actual code with some dev who had put this volatile qualifier thinking it would be needed, while it was definitely not).

So, yes, from that point on, it's all purely hypothetical, but the bottom line is that volatile for qualifying a function's return value in C is meaningless, and qualifying the function itself is not a thing, apart from "static".

I haven't yet looked into the new attributes westfw talks about in his last post above. But from what I get, those would be attributes, and not qualifiers. And nothing about being "volatile".
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf