9

I'm using Xcode 4.6 and I've got a header file which includes some constants I use throughout my code. I don't want to use preprocessor directives because I want them to be properly typed and such.

For example, I have this code in one of my .h files:

static NSString *kErrorCannotDivideByZero = @"Error: Cannot divide by zero";

and I use it in the corresponding .m file:

[self showToast:kErrorCannotDivideByZero];

I get the warning:

/path/to/my/headerFile.h:32:18: Unused variable 'kErrorCannotDivideByZero'

I know it's just a warning, but I've got about 50 of these warnings clogging up my compiler output.

Why am I getting this warning and how do I PROPERLY resolve it?

I'm not interested in simply suppressing all unused variable warnings, because I do want to get the legit ones.

Kenny Wyland
  • 20,844
  • 26
  • 117
  • 229
  • 1
    Are you sure that having static NSStrings in HEADER files instead of in .m files (and having just an extern static NSString in the header) isn't the CAUSE of your issue? I suspect that a different static instance is generated EACH time you include this into an .m file. – Warren P Feb 19 '13 at 00:16
  • I'd like to have some reusable strings that I can use throughout my app. What would you suggest? – Kenny Wyland Feb 19 '13 at 00:19
  • As Josh said below, "extern NSString *foo;" in headers and then declare the real non-static instance ONCE in the .m. Alternatively just #define FOO @"StringLiteral" – Warren P Feb 19 '13 at 00:19
  • As I said, I want to avoid preprocessors. Re: Defining the variables in the .h file and then declaring them for real in the .m file: If I want to use that variable in other sections of my app, am I going to have to declare it over and over again in each file? I haven't had my head deep in real C code since college in the 90s. – Kenny Wyland Feb 19 '13 at 00:25
  • @KennyWyland: See my answer. Basically, the header will just tell the compiler that a variable with that name exists, so you can include it in any source file. It's not the compiler's job to enforce whether it actually exists or not, only that you are using the variable correctly. It is the linker's job to ensure that all references to `kErrorCannotDivideByZero` actually point somewhere, and the way to do that is to define it somewhere in a source file (but just one) so that the linker can see it and go "Oh, there it is!" – dreamlax Feb 19 '13 at 00:30
  • 1
    OI. Extern in header. Non extern once in .m. Done. YOu cannot avoid the preprocessor. Both #define and #import and #include use it. Learn to love it. – Warren P Feb 19 '13 at 00:31

6 Answers6

17

Make the declaration in your header extern rather that static. What you're doing is creating a variable for every translation unit that includes your header, and this is why Clang is warning you, because it is legitimately a defined variable that is not being used. The extern keyword tells the compiler that the definition of the variable is found somewhere else (it might be in the same translation unit or it might be in another).

In your header, have:

// declare that the constant exists somewhere
extern NSString * const kErrorCannotDivideByZero;

And in one of your .m files (typically the one that shares the same name as the header), put

// define the constant, i.e. this is where it exists
NSString * const kErrorCannotDivideByZero = @"Error: Cannot divide by zero";

Declaring variables extern allows the compiler to ensure you are treating the variable correctly even if it doesn't know where it is defined (e.g. you can't use it as an NSArray). The linker has the job of making sure you actually defined it somewhere.

dreamlax
  • 93,976
  • 29
  • 161
  • 209
  • If Foo.h has the extern and Foo.m defines the content it points to... can Bar.m #import Foo.h and be able to use kErrorCannotDivideByZero? – Kenny Wyland Feb 19 '13 at 01:16
  • @KennyWyland: Yes, absolutely. The declaration in the header is only telling the compiler that an object of type `NSString *` called `kErrorCannotDivideByZero` exists somewhere. The linker (which links all of your compiled source files together) will resolve all of the references to the *one* definition. – dreamlax Feb 19 '13 at 01:34
  • Just to expand on this solution: what I usually do is declare my extern variables in, say, MyIncludes.h. Doesn't need to be const; `extern NSString* EnglishHiddenKey;` will do fine. Now I import MyIncludes.h into the .pch file so everybody sees it globally, and I define my externs in MyIncludes.m, e.g. `NSString* EnglishHiddenKey = @"englishHidden";` – matt Feb 19 '13 at 02:06
  • @matt: It doesn't need to be `const`, but making it `const` prevents the value from changing at runtime. – dreamlax Mar 25 '16 at 21:17
13

Clang will allow you to push and pop warning flags onto and from a "diagnostic" stack: "Controlling diagnostics via pragmas". You can wrap certain pieces of code like this:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-variable"

static NSString *kErrorCannotDivideByZero = @"Error: Cannot divide by zero";

#pragma clang diagnostic pop

to tell Clang that you know these aren't used, and that's okay in this particular instance.

Incidentally, you may not want to define these variables in a file that's being imported to many different places -- that's a good way to cause linker errors about variable redefinition (although this would only happen if the variable were globally linked -- declared/defined without static). The usual pattern for constants like this is to put an extern declaration in the header, and define the variable in another file. See Referencing a static NSString * const from another class for details.

As dreamlax has pointed out, you're actually getting these warnings because each file which imports your header is getting its own copy of the static variable; when I suggested the #pragma technique above, I was misunderstanding what you were asking for.

Community
  • 1
  • 1
jscs
  • 63,694
  • 13
  • 151
  • 195
4

Make your constants const:

static NSString * const kErrorCannotDivideByZero = @"Error: Cannot divide by zero";

(and as others pointed out, use extern and define in the implementation file)

Sebastian
  • 7,670
  • 5
  • 38
  • 50
  • +1: Not only does Apple use `const` in their headers, but it also means that you can't reassign `kErrorCannotDivideByZero` accidentally. – dreamlax Feb 19 '13 at 00:04
  • I originally had `const` in my code, but I received a lot of warnings about mismatched types when passing NSStrings to various apis. – Kenny Wyland Feb 19 '13 at 01:21
  • 4
    That usually happens when you declare as `const NSString *` (pointer to const) instead of `NSString * const` (constant pointer) – Sebastian Feb 19 '13 at 01:31
  • Thanks Sebastian, that was a difference I did not think about. – Kenny Wyland Feb 19 '13 at 02:24
2

Perhaps, rather than initialising them to string literals, you could run an initialise function that loads these values from a locale-specific file so that errors are in a translated language. When your initialise function assigns to that variable, your compiler might be tempted to believe that the variable needs to exist for the compile to be successful.

autistic
  • 1
  • 3
  • 35
  • 80
  • It would be easier to provide a `Localizable.strings` file to translate errors. – dreamlax Feb 18 '13 at 23:53
  • It would be silly to assume that the OP doesn't want an answer he/she can use in his/her C projects, too, since he/she also tagged "C". – autistic Feb 18 '13 at 23:56
  • I don't think it's silly at all. Objective-C is a superset of C and although the code provided is obviously Objective-C, the warnings he is getting is because of Objective-C's roots in C. Also, even for C projects there are already solutions out there for localisation e.g. GNU `gettext`. – dreamlax Feb 19 '13 at 00:03
  • I think you misunderstood my last comment. Allow me to reiterate it: Perhaps the OP also programs in C, and wants a solution that would also work in C, without the issues of a third-party library that uses commercially restrictive licensing. – autistic Feb 19 '13 at 00:10
  • That's a massive assumption to make from a `c` tag on an Objective-C question about unused variables. Your answer will still not make a difference because even if you load the strings from an external file in one translation unit, you will not see any difference in any other translation units because the variable is declared `static`. So not only will the other translation units not see any effect of loading the strings externally, but they will also continue to invoke the unused variable warning. – dreamlax Feb 19 '13 at 00:25
  • It will make a huge difference, if each translation unit calls the initialise function. Are you criticising my answer because I'm providing an alternative to your answer that seems to make slightly less sense? – autistic Feb 19 '13 at 00:35
  • Have you considered the possibility that each translation unit may wish to use different error messages, @dreamlax? – autistic Feb 19 '13 at 00:36
  • You're right, it will make a huge difference. It will make a huge difference to loading times and source maintainability. You'd have to remember to include an initialisation function with external linkage in every source file that uses the string (and a corresponding declaration in its header) that can be called from `main` before you make use of that string, not to mention that each source file would also have to manage its own memory for these dynamically loaded strings. It's an over-engineered solution to a simple problem. – dreamlax Feb 19 '13 at 00:58
  • "It will make a huge difference to loading times and source maintainability." In the scheme of things, how much time are you talking about? A few microseconds? "It's an over-engineered solution to a simple problem." The problem isn't sufficiently specified to derive a specific solution, hence the presense of three unique answers. You've provided one answer, but it's not the only answer. Let it go. – autistic Feb 19 '13 at 01:47
  • Sorry my bad, went a little overboard with the comments. – dreamlax Feb 19 '13 at 02:29
1

GCC (and I assume clang) don't warn about unused constants. One trap to look out for here is that pointers need to be const pointers, not just pointers to const; so, to properly declare an unused string constant that won't trigger any warnings, you need:

const char * const myConst = "myConst";
Tom
  • 7,269
  • 1
  • 42
  • 69
0

You could move all the static variable declaration to your respective .m file. That should take away all those "unused variable" warnings. The reason being that static variables are limited to a file level scope.