Constant strings

B

BartC

Stephen Sprunk said:
It'd be annoying if every time we called a function, we had to make a
copy of all the data just in case that function tried to modify it; in
most cases, the function won't so that effort is wasted. If only there
were a way for a function to indicate that it won't do so, which
compilers could then enforce for us... Oh wait, there is; it's called
"const"!

OK. How would that work with the Image_t example I gave in another post?

Image_t is a type which specifies an image, but doesn't go into any details
as to its implementation.

You want to call an external function SomeFunction(Image_t) which does not
modify the image it is passed. How would that be expressed using const?
No, const helps _prevent_ and _diagnose_ real bugs.

If you say so.
 
J

James Kuyper

On 04/22/2014 01:48 PM, BartC wrote:
....
You want to call an external function SomeFunction(Image_t) which does not
modify the image it is passed. How would that be expressed using const?

typedef struct Image Image_t;
// struct Image is an opague type.

somereturntype SomeFunction(const Image_t *);
 
K

Keith Thompson

BartC said:
Keith Thompson said:
BartC said:
You might try casting the argument to (const T*), but then you just get a
compiler error here; what good will that do? You need to call F, so a
solution has to be found. And you wouldn't have deliberately put in this
cast unless you'd already researched the side-effects of F, in which case
you don't need the compiler to tell you what you already know!

Or maybe you routinely just use (const T*) casts everywhere you ever pass
a
pointer to anything, but then I wouldn't want to have to read your code!

Actually I can't see point of using 'const T*' as any function parameter
type, since it will accept both const and non-const arguments (but see
below).
[...]

More briefly (since this point may have been lost in previous walls of
text):

If you think that casting a T* argument to (const T*) does anything
at all, or if you think that anyone else in this discussion has
advocated such casts, then I submit that you do not properly
understand what "const" means in C as it's currently defined.

You think so?
Yes.

My remark was about casting a T* argument to const T*, when passed to a
function taking a T* parameter.

It doesn't exactly do nothing:

strcpy("abc","def");

generates no warnings from gcc, but applying a cast does:

strcpy((const char*)"abc","def");

That's caused by string literals not being const, which I've already
acknowledged is a flaw in the language (but an unavoidable one).

I think you'll find fewer string literals in production code than in
demo code and code written by students. String data is more commonly
taken from command-line arguments, files, and so forth. And any data
that shouldn't be modified should be defined as "const" in the first
place, so no cast is needed.

For non-toy programs, rather than using a string literal directly, you
can use it as the initializer for a "const char*" or "const char[]"
object.

const char *target = "abc";
const char *source = "def";
strcpy(target, source); /* triggers a diagnostic */
A bit of an imposition though to add everywhere.

Nobody has suggested adding casts "everywhere". Casts might be
necessary in some *rare* cases where you actually need to override
const.

Do you have an example not involving string literals?
I advise you to correct that gap in your knowledge before criticizing
the current language definition. I'll be happy to help with any
questions.
A concrete example:

char message[] = "hello";
strlen(message);

message is non-const. strlen has a const char* parameter. No cast is
needed on the call, and adding such a cast would do nothing.

Indeed. But that is not what I said, which was more along the lines of the
const in the 'const char*' formal parameter type of strlen() doing nothing,
in terms of generating warnings. (Although I will admit it might help out
the compiler with its optimising.)

The "const" on the definition of strlen's parameter does not result
in any additional warnings. Omitting the "const" *would* result
in additional warnings.

For example:

size_t hypothetical_strlen(char *s);
const char *message = "hello";
strlen(message); /* ok */
hypothetical_strlen(message); /* triggers a diagnostic */

The diagnostic occurs because, even though hypothetical_strlen()
presumably won't attempt to modify the string, its author failed to
specify that.

Now you could omit the const on the definition of "message" (and in fact
that was the only option before const was added to the language by the
1989 ANSI C standard). But then you would have no way to assert that an
object will not be modified, or that a function will not attempt to
modify an object whose address is passed to it.
 
B

BartC

James Kuyper said:
On 04/22/2014 01:48 PM, BartC wrote:
...

typedef struct Image Image_t;
// struct Image is an opague type.

somereturntype SomeFunction(const Image_t *);

Nice try, but:

* As you have it, Image_t is now not so opaque; you've introduced a struct
and a pointer

* As written, the const here doesn't stop SomeFunction from changing the
image data (it might make it harder to change details in the descriptor,
*if* implemented as a pointer to a struct).

I suppose you could consider this 'const' to be just a token, informal way
of telling a human reader that it will not modify the image data. But in
that case a comment would do just as well.
 
B

BartC

Keith Thompson said:
Do you have an example not involving string literals?

void change_array(int* a) { a[0]=9999999;}
.....
int data[]={10,20,30,40};

data[2]=rand();
change_array(data);
change_array((const int*)data);

The latter call generates a warning, the former doesn't.

You said this:

"If you think that casting a T* argument to (const T*) does anything
at all, or if you think that anyone else in this discussion has
advocated such casts, then I submit that you do not properly
understand what "const" means in C as it's currently defined."

Obviously applying such a cast can be made to do something.
 
K

Keith Thompson

BartC said:
Keith Thompson said:
Do you have an example not involving string literals?

void change_array(int* a) { a[0]=9999999;}
....
int data[]={10,20,30,40};

data[2]=rand();
change_array(data);
change_array((const int*)data);

The latter call generates a warning, the former doesn't.

You said this:

"If you think that casting a T* argument to (const T*) does anything
at all, or if you think that anyone else in this discussion has
advocated such casts, then I submit that you do not properly
understand what "const" means in C as it's currently defined."

Obviously applying such a cast can be made to do something.

You're right, my mistake. Adding a (const int*) cast to an argument
when the parameter is defined as "int*" does trigger a warning.
(I was thinking of the case where the parameter is defined as
"const int*".) I apologize for the confusion.

But why would you add such a cast in the first place?

I think you introduced the idea of adding numerous (const something*)
casts to function arguments as a way to fix or work around some
perceived problem.
 
J

James Kuyper

Nice try, but:

* As you have it, Image_t is now not so opaque; you've introduced a struct
and a pointer

If it's not opaque, how many members does it have? What are their names
and types, and in what order are they allocated? Keep in mind that no
more information need be given to the compiler, than I've already given
you, in order to write code that passes around Image_t* values. The fact
that I've used a struct is not something the user needs to know about -
it could just as easily have been a union or an arithmetic type (though
it would have to be a pretty small image to fit in a single object of a
standard arithmetic type - a long double _Complex would typically not be
large enough for anything more than a 16x10 b/w image).

If you need more opacity than that, you'll have to explain why. Hiding
the fact that it's a pointer behind a typedef is TOO MUCH opacity,
because it can cause things to be syntax errors that don't look like
syntax errors (and vice versa). If you want to use an array type, you
can store it inside the struct. Making Image_t itself an array type
would cause it to be unexpectedly treated as a pointer in some contexts.
* As written, the const here doesn't stop SomeFunction from changing the
image data (it might make it harder to change details in the descriptor,
*if* implemented as a pointer to a struct).

As written, it is a pointer to a struct, and therefore requires
SomeFunction() to cast away the 'const' in order to change the value of
any member of that struct. I always treat casts with suspicion, as
something that should be avoided, so that's sufficient to be useful
(though I'd prefer a new language feature that would cause such casts to
be constraint violations).

This approach can't do anything about structs that contain pointers to
the actual data managed by the struct, or indices into arrays that are
not part of the struct. That reduces the value of using 'const' for this
purpose, but it does not eliminate that value - it doesn't even come
close to doing so. Rather, it reduces (but does not eliminate) the value
of using such structures for managing the data.
I suppose you could consider this 'const' to be just a token, informal way
of telling a human reader that it will not modify the image data. But in
that case a comment would do just as well.

For the purposes of my response, consider:

someotherreturntype SomeOtherFunction(Image_t *);

A comment in place of the 'const' in the declaration of SomeFunction()
would not cause a mandatory diagnostic if SomeFunction() attempts to
modify the object pointed at, including, as a special case, any attempt
to pass that pointer to SomeOtherFunction(). Therefore, a comment is not
an adequate substitute for 'const'.
 
S

Stephen Sprunk

You think so?

My remark was about casting a T* argument to const T*, when passed to
a function taking a T* parameter.

It doesn't exactly do nothing:

strcpy("abc","def");

generates no warnings from gcc, but applying a cast does:

strcpy((const char*)"abc","def");

A bit of an imposition though to add everywhere.

That particular case is due to string literals not being const in C for
historical reasons, as has been previously explained to you. If they
were const (as in C++, AIUI), you wouldn't need the cast to get an error.
I advise you to correct that gap in your knowledge before
criticizing the current language definition. I'll be happy to help
with any questions.
A concrete example:

char message[] = "hello";
strlen(message);

message is non-const. strlen has a const char* parameter. No cast
is needed on the call, and adding such a cast would do nothing.

Indeed. But that is not what I said, which was more along the lines
of the const in the 'const char*' formal parameter type of strlen()
doing nothing, in terms of generating warnings. (Although I will
admit it might help out the compiler with its optimising.)

strlen() doesn't take a const argument to generate warnings; it does so
to allow passing a const argument _without_ generating a warning. You
can always pass a non-const argument if a const one is expected.

S
 
I

Ian Collins

BartC said:
I wasn't specifically generating C++ code. I just tried a C++ compiler on
the off-chance it would work.

For example C++ doesn't like pointers to unbounded arrays, which my source
language likes to use to differentiate between pointers to arrays and
non-arrays (more useful than const/non-const IMO).

Fixing that isn't so trivial.

Care to provide an example?
 
B

BartC

Ian Collins said:
Care to provide an example?

Fragment of source syntax:

proc testfn(ref[]int a)=
print a^[2] # (ie. (*a)[2]; explicit deref needed)
end

proc start=
[]int a:=(10,20,30,40,50)

testfn(&a)
end

C output (there is also a header explaining what i32 is etc):

static void testfn(i32 (*a)[]) {
printf("%d",(*a)[1]); /* (adjusts 1-based to 0-based indexing) */
}

void start(void) {
i32 a[5]={10,20,30,40,50};
testfn((i32 (*)[])(&a));
}

This compiles as C (with gcc, Pelles C, lccwin32, DMC, gcc/TDM, and Clang
x86 compilers).

But g++ says this:

.... error: parameter 'a' includes pointer to array of unknown bound 'i32 []
{aka int []}'
static void testfn(i32 (*a)[]) {
 
I

Ian Collins

BartC said:
Ian Collins said:
Care to provide an example?

Fragment of source syntax:

proc testfn(ref[]int a)=
print a^[2] # (ie. (*a)[2]; explicit deref needed)
end

proc start=
[]int a:=(10,20,30,40,50)

testfn(&a)
end

C output (there is also a header explaining what i32 is etc):

static void testfn(i32 (*a)[]) {
printf("%d",(*a)[1]); /* (adjusts 1-based to 0-based indexing) */
}

void start(void) {
i32 a[5]={10,20,30,40,50};
testfn((i32 (*)[])(&a));
}

This compiles as C (with gcc, Pelles C, lccwin32, DMC, gcc/TDM, and Clang
x86 compilers).

But g++ says this:

.... error: parameter 'a' includes pointer to array of unknown bound 'i32 []
{aka int []}'
static void testfn(i32 (*a)[]) {

I'm surprised gcc doesn't issue a warning about the null dimension.

You could avoid all of the casts if you wrote this in C++:

template <int N> void testfn(int (&a)[N]) {
printf("%d\n",a[1]); /* (adjusts 1-based to 0-based indexing) */
}

void start(void) {
int a[5]={10,20,30,40,50};
testfn(a);
}

Note the parameter type "int (&a)[N]" is a reference to an array of N
ints, the compiler does the rest! This is a useful trick for forwarding
array sizes to functions that use a pointer and a size as their parameters.
 
S

Stephen Sprunk

OK. How would that work with the Image_t example I gave in another
post?

Image_t is a type which specifies an image, but doesn't go into any
details as to its implementation.

You want to call an external function SomeFunction(Image_t) which
does not modify the image it is passed. How would that be expressed
using const?

// image.h
typedef /* something */ Image_t;
SomeFunction(const Image_t *img);

// caller.c
#include "image.h"
void myfunc(void) {
Image_t *x = /* something */;
const Image_t *y = x;
SomeFunction(x); // no error
SomeFunction(y); // no error
}

If SomeFunction() needs to modify the image passed, then SomeFunction(y)
can't take a const argument. So, you would modify the interface like this:

// image.h
typedef /* something */ Image_t;
CopyImage(Image_t *dst, const Image_t *src);
SomeFunction(Image_t *img);

// caller.c
#include "image.h"
void myfunc(void) {
Image_t *x = /* something */, *y;
CopyImage(y, x);
SomeFunction(x); // no error
SomeFunction(y); // no error
}

S
 
S

Stephen Sprunk

Nice try, but:

* As you have it, Image_t is now not so opaque; you've introduced a
struct and a pointer

A pointer-to-incomplete-struct is idiomatic for opaque data in C.
* As written, the const here doesn't stop SomeFunction from changing the
image data (it might make it harder to change details in the descriptor,
*if* implemented as a pointer to a struct).

That wasn't the point; the point was to allow passing both const and
non-const images to SomeFunction(), which promised to not modify the
image by marking its argument as const.

The implementation of SomeFunction() could do all sorts of nasty things
to break that promise, e.g. casting away the constness of its argument.
C doesn't prevent doing that, nor would such be desirable because there
are (rare) cases where that is actually the right thing to do.

S
 
B

BartC

Stephen Sprunk said:
On 22-Apr-14 13:48, BartC wrote:

That wasn't the point; the point was to allow passing both const and
non-const images to SomeFunction(), which promised to not modify the
image by marking its argument as const.

So, const now is only being used as a kind of gentleman's agreement between
caller and callee, to make certain promises about whether the internal data
will be protected or not. Because you can't fully protect the data without
having const attributes buried deep inside the opaque type; a top-level
'const' is being used as a guard.

(Which is similar to what I suggested in other thread, to avoid crazy
constructions such as 'const int * const (* const x)[]')

That's fine, maybe it can become number (3) on jacob navia's list of
different categories of 'const' (I can think of one or two more).

However, when I try and use such a guard to suggest that a file-handling
function will not modify the file I'm passing it, I run into problems:

void dont_update_the_file(const FILE* f) {
fseek(f,0,SEEK_SET);
}

The fseek() call generates a warning. So it needs every single function,
that might take such a parameter, to cooperate with the scheme.

But do people actually make use of const when working with file-processing
functions? If not, then they might well find that not bothering with const
works elsewhere too!
The implementation of SomeFunction() could do all sorts of nasty things
to break that promise, e.g. casting away the constness of its argument.

It doesn't have to do anything nasty at all. In the code below,
dont_write_to_image() manages to populate the image data without any
complaint from the compiler:

#include <stdio.h>
#include <stdlib.h>

typedef struct {
int dimx,dimy;
char* data;
} imgdescr;

void dont_write_to_image(const imgdescr* bm){
int i;
for (i=0; i<bm->dimx*bm->dimy; ++i)
bm->data=rand()&1?'1':'0';
}

int main (void) {
imgdescr bm = {16,16,NULL};
imgdescr* ibm=&bm;

ibm->data = malloc(ibm->dimx*ibm->dimy);

dont_write_to_image(ibm);
}

There *would* be a complaint if, instead, it used a setpixel() routine which
didn't have the const guard. So some sort of protection might work, but it
requires considerable extra effort, and is much harder if many of the
functions involved are not yours and haven't implemented the same scheme.

TBH, I would much rather accept responsibility for my own programming errors
if I inadvertently call the wrong functions on my data, than waste time
building a protection scheme based on a half-baked language feature, that is
of doubtful benefit anyway.
 
B

Ben Bacarisse

Ian Collins said:
BartC wrote:
Fragment of source syntax:

proc testfn(ref[]int a)=
print a^[2] # (ie. (*a)[2]; explicit deref needed)
end

proc start=
[]int a:=(10,20,30,40,50)

testfn(&a)
end

C output (there is also a header explaining what i32 is etc):

static void testfn(i32 (*a)[]) {
printf("%d",(*a)[1]); /* (adjusts 1-based to 0-based indexing) */
}

void start(void) {
i32 a[5]={10,20,30,40,50};
testfn((i32 (*)[])(&a));
}

This compiles as C (with gcc, Pelles C, lccwin32, DMC, gcc/TDM, and Clang
x86 compilers).

But g++ says this:

.... error: parameter 'a' includes pointer to array of unknown bound 'i32 []
{aka int []}'
static void testfn(i32 (*a)[]) {

I'm surprised gcc doesn't issue a warning about the null dimension.

(int (*)[]) and (int (*)[5]) are compatible types so there is nothing
reasonable to warn about.
You could avoid all of the casts if you wrote this in C++:

He could avoid all of the casts in C by simply not writing them!
(There's only one that I can see, and it's not needed.)

<snip>
 
B

Ben Bacarisse

BartC said:
So, const now is only being used as a kind of gentleman's agreement
between caller and callee, to make certain promises about whether the
internal data will be protected or not.

Yes, that's it. Casts (and a few other historical oddities) allow you
to circumvent the type system in C.

That's fine, maybe it can become number (3) on jacob navia's list of
different categories of 'const' (I can think of one or two more).

Jacob's classification was an invention. It does not describe cont as
the C defines it where it has (as far as I can tell) one meaning: that
you can't modify an object using an expression whose type is const
qualified. It really not complicated.
However, when I try and use such a guard to suggest that a
file-handling function will not modify the file I'm passing it, I run
into problems:

void dont_update_the_file(const FILE* f) {
fseek(f,0,SEEK_SET);
}

The fseek() call generates a warning. So it needs every single
function, that might take such a parameter, to cooperate with the
scheme.

Of course. fseek can't make the promise that won't modify the FILE
object so you can't use in a function like dont_update_the_file that
does make such a promise.

I think your objection stems from the fact that const is either not what
you thought it was, or not what you would like it to be.

You offer an example that demonstrates this:
In the code below,
dont_write_to_image() manages to populate the image data without any
complaint from the compiler:

#include <stdio.h>
#include <stdlib.h>

typedef struct {
int dimx,dimy;
char* data;
} imgdescr;

void dont_write_to_image(const imgdescr* bm){
int i;
for (i=0; i<bm->dimx*bm->dimy; ++i)
bm->data=rand()&1?'1':'0';
}


I don't know whether you are surprised or disappointed here (i.e. I
don't know if you thought the "const" would prevent this, or if you
thought it should prevent it) but I am neither. const imgdescr* bm just
means that *bm is not a modifiable lvalue expression. It does not mean
that the function can't modify the object (as Jacob claimed) since there
may be some other way to get at it, not does it mean that all of the
expression derived from *bm (like bm->data) are not modifiable.

<snip>
 
J

James Kuyper

....
Jacob's classification was an invention. It does not describe cont as
the C defines it where it has (as far as I can tell) one meaning: that
you can't modify an object using an expression whose type is const
qualified. It really not complicated.

That's Jacob's first meaning. This is an example of Jacob's second meaning:

const int i=5;
*(int *)&i = 6; // Undefined behavior (6.7.3p6)
 
J

James Kuyper

On 04/23/2014 06:56 AM, BartC wrote:
.....
However, when I try and use such a guard to suggest that a file-handling
function will not modify the file I'm passing it, I run into problems:

void dont_update_the_file(const FILE* f) {
fseek(f,0,SEEK_SET);
}

The fseek() call generates a warning. So it needs every single function,
that might take such a parameter, to cooperate with the scheme.

It's supposed to generate a warning. That's precisely what the 'const'
is intended to achieve - preventing you from doing anything that would
modify *f. Since fseek() needs to update some of the members of the FILE
that it is passed, (specifically, the ones that keep track of the
current position in the file), the fact that a diagnostic message is
required for that call is how the 'const' achieves precisely that effect.

Your mistake lies in assuming that the 'const' protects the file managed
by the FILE object. It only protects the FILE object itself. Since some
elements of that object may need to be updated any time you write to the
file managed by that structure, protecting the FILE object does in fact
prevent you from modifying the file that it manages. However, it also
prevents you from doing a lot of other things that don't modify the
file, such as reading it. I think that the only operations that could,
in principle, be carried out on a FILE without modifying it are feof()
and ferror(). However, those functions are both defined as taking
"FILE*" rather than "const FILE*" parameters. That's probably because a
'const FILE*' is so nearly useless it never occurred to anyone that
there might be any point in using it for those two functions.
The implementation of SomeFunction() could do all sorts of nasty things
to break that promise, e.g. casting away the constness of its argument.

It doesn't have to do anything nasty at all. In the code below,
dont_write_to_image() manages to populate the image data without any
complaint from the compiler:

#include <stdio.h>
#include <stdlib.h>

typedef struct {
int dimx,dimy;
char* data;
} imgdescr;

void dont_write_to_image(const imgdescr* bm){
int i;
for (i=0; i<bm->dimx*bm->dimy; ++i)
bm->data=rand()&1?'1':'0';
}


The 'const' only protects the members of 'bm'; expecting anything else
to be protected (such as data pointed by one of the members of 'bm') is
a mistake. If data had been an array, rather than a pointer, that code
would have been a constraint violation.
I've already acknowledged this issue, without accepting your claim that
it renders 'const' useless.
 
B

BartC

Ben Bacarisse said:
Ian Collins said:
BartC wrote:
static void testfn(i32 (*a)[]) {
printf("%d",(*a)[1]); /* (adjusts 1-based to 0-based indexing)
*/
}

void start(void) {
i32 a[5]={10,20,30,40,50};
testfn((i32 (*)[])(&a));
But g++ says this:

.... error: parameter 'a' includes pointer to array of unknown bound
'i32 []
{aka int []}'
static void testfn(i32 (*a)[]) {

I'm surprised gcc doesn't issue a warning about the null dimension.

(int (*)[]) and (int (*)[5]) are compatible types so there is nothing
reasonable to warn about.
You could avoid all of the casts if you wrote this in C++:

He could avoid all of the casts in C by simply not writing them!
(There's only one that I can see, and it's not needed.)

I was surprised myself. The reason seems to be that the cast is there to
convert from:

'pointer to array 5 of int'

to:

'pointer to array of int'

using Cdecl syntax.

(The source language needs arrays to be compatible (they are value types);
but it also generally doesn't care about pointer targets needing to match.
The cast must be there to keep C compilers happy. But if C doesn't need it,
that doesn't really matter. The translator generates a lot worse! This bit
of input source code:

byte swapped
repeat
until not swapped

results in this C (which I have even tidied up by removing unnecessary
labels):

u8 swapped;
do {
} while (!(!((bool)((u32)(swapped)))));

My C compilers might be suffering but they haven't complained about it so
far.)
 
B

Ben Bacarisse

[...] But if C doesn't need it,
that doesn't really matter. The translator generates a lot worse!

Yes, that's why I didn't reply to your post but to Ian's comment on it.
It's not surprising that a translator's output has redundant constructs
like casts and parentheses -- it's just simpler to do it that way, but
Ian seemed to be suggesting that C++ could be used to avoid the cast,
so I thought it helpful to point out that it is not needed, even in C.

<snip>
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,756
Messages
2,569,540
Members
45,025
Latest member
KetoRushACVFitness

Latest Threads

Top